create and restore backups
This commit is contained in:
parent
6ae463a784
commit
f78097b616
16 changed files with 696 additions and 21 deletions
|
@ -47,6 +47,7 @@ export class calendar {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "RESTRICT",
|
onDelete: "RESTRICT",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
type: calendarType;
|
type: calendarType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ export class communication {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "RESTRICT",
|
onDelete: "RESTRICT",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
type: communicationType;
|
type: communicationType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,26 +29,27 @@ export class member {
|
||||||
@Column()
|
@Column()
|
||||||
salutationId: number;
|
salutationId: number;
|
||||||
|
|
||||||
@OneToMany(() => communication, (communications) => communications.member)
|
|
||||||
communications: communication[];
|
|
||||||
|
|
||||||
@ManyToOne(() => salutation, (salutation) => salutation.members, {
|
@ManyToOne(() => salutation, (salutation) => salutation.members, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "RESTRICT",
|
onDelete: "RESTRICT",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
salutation: salutation;
|
salutation: salutation;
|
||||||
|
|
||||||
@OneToMany(() => membership, (membership) => membership.member)
|
@OneToMany(() => communication, (communications) => communications.member, { cascade: ["insert"] })
|
||||||
|
communications: communication[];
|
||||||
|
|
||||||
|
@OneToMany(() => membership, (membership) => membership.member, { cascade: ["insert"] })
|
||||||
memberships: membership[];
|
memberships: membership[];
|
||||||
|
|
||||||
@OneToMany(() => memberAwards, (awards) => awards.member)
|
@OneToMany(() => memberAwards, (awards) => awards.member, { cascade: ["insert"] })
|
||||||
awards: memberAwards[];
|
awards: memberAwards[];
|
||||||
|
|
||||||
@OneToMany(() => memberExecutivePositions, (executivePositions) => executivePositions.member)
|
@OneToMany(() => memberExecutivePositions, (executivePositions) => executivePositions.member, { cascade: ["insert"] })
|
||||||
positions: memberExecutivePositions[];
|
positions: memberExecutivePositions[];
|
||||||
|
|
||||||
@OneToMany(() => memberQualifications, (qualifications) => qualifications.member)
|
@OneToMany(() => memberQualifications, (qualifications) => qualifications.member, { cascade: ["insert"] })
|
||||||
qualifications: memberQualifications[];
|
qualifications: memberQualifications[];
|
||||||
|
|
||||||
firstMembershipEntry?: membership;
|
firstMembershipEntry?: membership;
|
||||||
|
|
|
@ -33,6 +33,7 @@ export class memberAwards {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "RESTRICT",
|
onDelete: "RESTRICT",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
award: award;
|
award: award;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ export class memberExecutivePositions {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "RESTRICT",
|
onDelete: "RESTRICT",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
executivePosition: executivePosition;
|
executivePosition: executivePosition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ export class memberQualifications {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "RESTRICT",
|
onDelete: "RESTRICT",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
qualification: qualification;
|
qualification: qualification;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ export class membership {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "RESTRICT",
|
onDelete: "RESTRICT",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
status: membershipStatus;
|
status: membershipStatus;
|
||||||
|
|
|
@ -30,16 +30,17 @@ export class newsletter {
|
||||||
@Column({ type: "int", nullable: true })
|
@Column({ type: "int", nullable: true })
|
||||||
recipientsByQueryId?: number;
|
recipientsByQueryId?: number;
|
||||||
|
|
||||||
@OneToMany(() => newsletterDates, (dates) => dates.newsletter)
|
@OneToMany(() => newsletterDates, (dates) => dates.newsletter, { cascade: ["insert"] })
|
||||||
dates: newsletterDates[];
|
dates: newsletterDates[];
|
||||||
|
|
||||||
@OneToMany(() => newsletterRecipients, (recipient) => recipient.newsletter)
|
@OneToMany(() => newsletterRecipients, (recipient) => recipient.newsletter, { cascade: ["insert"] })
|
||||||
recipients: newsletterRecipients[];
|
recipients: newsletterRecipients[];
|
||||||
|
|
||||||
@ManyToOne(() => query, {
|
@ManyToOne(() => query, {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
onDelete: "CASCADE",
|
onDelete: "CASCADE",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
recipientsByQuery?: query;
|
recipientsByQuery?: query;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { Column, Entity, PrimaryColumn } from "typeorm";
|
import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm";
|
||||||
|
import { protocolAgenda } from "./protocolAgenda";
|
||||||
|
import { protocolDecision } from "./protocolDecision";
|
||||||
|
import { protocolPresence } from "./protocolPresence";
|
||||||
|
import { protocolPrintout } from "./protocolPrintout";
|
||||||
|
import { protocolVoting } from "./protocolVoting";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class protocol {
|
export class protocol {
|
||||||
|
@ -19,4 +24,19 @@ export class protocol {
|
||||||
|
|
||||||
@Column({ type: "text", nullable: true })
|
@Column({ type: "text", nullable: true })
|
||||||
summary: string;
|
summary: string;
|
||||||
|
|
||||||
|
@OneToMany(() => protocolAgenda, (agenda) => agenda.protocol, { cascade: ["insert"] })
|
||||||
|
agendas: protocolAgenda[];
|
||||||
|
|
||||||
|
@OneToMany(() => protocolDecision, (decision) => decision.protocol, { cascade: ["insert"] })
|
||||||
|
decisions: protocolDecision[];
|
||||||
|
|
||||||
|
@OneToMany(() => protocolPresence, (presence) => presence.protocol, { cascade: ["insert"] })
|
||||||
|
presences: protocolPresence[];
|
||||||
|
|
||||||
|
@OneToMany(() => protocolPrintout, (printout) => printout.protocol, { cascade: ["insert"] })
|
||||||
|
printouts: protocolPrintout[];
|
||||||
|
|
||||||
|
@OneToMany(() => protocolVoting, (voting) => voting.protocol, { cascade: ["insert"] })
|
||||||
|
votings: protocolVoting[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { communicationType } from "./communicationType";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class newsletterConfig {
|
export class newsletterConfig {
|
||||||
@PrimaryColumn({ type: "int" })
|
@PrimaryColumn()
|
||||||
comTypeId: number;
|
comTypeId: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
|
@ -25,6 +25,7 @@ export class newsletterConfig {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "CASCADE",
|
onDelete: "CASCADE",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
comType: communicationType;
|
comType: communicationType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,6 @@ export class role {
|
||||||
})
|
})
|
||||||
users: user[];
|
users: user[];
|
||||||
|
|
||||||
@OneToMany(() => rolePermission, (rolePermission) => rolePermission.role)
|
@OneToMany(() => rolePermission, (rolePermission) => rolePermission.role, { cascade: ["insert"] })
|
||||||
permissions: rolePermission[];
|
permissions: rolePermission[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,13 @@ export class user {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "CASCADE",
|
onDelete: "CASCADE",
|
||||||
onUpdate: "RESTRICT",
|
onUpdate: "RESTRICT",
|
||||||
|
cascade: ["insert"],
|
||||||
})
|
})
|
||||||
@JoinTable({
|
@JoinTable({
|
||||||
name: "user_roles",
|
name: "user_roles",
|
||||||
})
|
})
|
||||||
roles: role[];
|
roles: role[];
|
||||||
|
|
||||||
@OneToMany(() => userPermission, (userPermission) => userPermission.user)
|
@OneToMany(() => userPermission, (userPermission) => userPermission.user, { cascade: ["insert"] })
|
||||||
permissions: userPermission[];
|
permissions: userPermission[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,6 @@ export class webapi {
|
||||||
@Column({ type: "date", nullable: true })
|
@Column({ type: "date", nullable: true })
|
||||||
expiry?: Date;
|
expiry?: Date;
|
||||||
|
|
||||||
@OneToMany(() => webapiPermission, (apiPermission) => apiPermission.webapi)
|
@OneToMany(() => webapiPermission, (apiPermission) => apiPermission.webapi, { cascade: ["insert"] })
|
||||||
permissions: webapiPermission[];
|
permissions: webapiPermission[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,9 @@ export const MAIL_SECURE = process.env.MAIL_SECURE ?? "false";
|
||||||
export const CLUB_NAME = process.env.CLUB_NAME ?? "FF Admin";
|
export const CLUB_NAME = process.env.CLUB_NAME ?? "FF Admin";
|
||||||
export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? "";
|
export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? "";
|
||||||
|
|
||||||
export const BACKUP_INTERVAL = Number(process.env.CLUB_WEBSITE ?? "0");
|
export const BACKUP_INTERVAL = Number(process.env.BACKUP_INTERVAL ?? "0");
|
||||||
export const BACKUP_COPIES = Number(process.env.CLUB_WEBSITE ?? "0");
|
export const BACKUP_COPIES = Number(process.env.BACKUP_COPIES ?? "0");
|
||||||
export const BACKUP_AUTO_RESTORE = process.env.CLUB_WEBSITE ?? "false";
|
export const BACKUP_AUTO_RESTORE = process.env.BACKUP_AUTO_RESTORE ?? "false";
|
||||||
|
|
||||||
export function configCheck() {
|
export function configCheck() {
|
||||||
if (DB_TYPE != "mysql" && DB_TYPE != "sqlite") throw new Error("set valid value to DB_TYPE (mysql|sqlite)");
|
if (DB_TYPE != "mysql" && DB_TYPE != "sqlite") throw new Error("set valid value to DB_TYPE (mysql|sqlite)");
|
||||||
|
|
638
src/helpers/backupHelper.ts
Normal file
638
src/helpers/backupHelper.ts
Normal file
|
@ -0,0 +1,638 @@
|
||||||
|
import { dataSource } from "../data-source";
|
||||||
|
import { FileSystemHelper } from "./fileSystemHelper";
|
||||||
|
import { EntityManager } from "typeorm";
|
||||||
|
import InternalException from "../exceptions/internalException";
|
||||||
|
import UserService from "../service/user/userService";
|
||||||
|
|
||||||
|
export type BackupSection =
|
||||||
|
| "member"
|
||||||
|
| "memberBase"
|
||||||
|
| "protocol"
|
||||||
|
| "newsletter"
|
||||||
|
| "newsletter_config"
|
||||||
|
| "calendar"
|
||||||
|
| "query"
|
||||||
|
| "template"
|
||||||
|
| "user"
|
||||||
|
| "webapi";
|
||||||
|
|
||||||
|
export type BackupSectionRefered = {
|
||||||
|
[key in BackupSection]?: Array<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BackupFileContent = { [key in BackupSection]?: BackupFileContentSection } & { collectIds: boolean };
|
||||||
|
export type BackupFileContentSection = Array<any> | { [key: string]: Array<any> };
|
||||||
|
|
||||||
|
export default abstract class BackupHelper {
|
||||||
|
// ! Order matters because of foreign keys
|
||||||
|
private static readonly backupSection: Array<{ type: BackupSection; orderOnInsert: number; orderOnClear: number }> = [
|
||||||
|
{ type: "member", orderOnInsert: 2, orderOnClear: 2 }, // CLEAR depends on protcol INSERT depends on Base
|
||||||
|
{ type: "memberBase", orderOnInsert: 1, orderOnClear: 3 }, // CLEAR depends on member
|
||||||
|
{ type: "protocol", orderOnInsert: 3, orderOnClear: 1 }, // INSERT depends on member
|
||||||
|
{ type: "newsletter", orderOnInsert: 3, orderOnClear: 1 }, // INSERT depends on member & query & calendar
|
||||||
|
{ type: "newsletter_config", orderOnInsert: 2, orderOnClear: 4 }, // INSERT depends on member com
|
||||||
|
{ type: "calendar", orderOnInsert: 1, orderOnClear: 1 },
|
||||||
|
{ type: "query", orderOnInsert: 1, orderOnClear: 2 }, // CLEAR depends on newsletter
|
||||||
|
{ type: "template", orderOnInsert: 2, orderOnClear: 1 }, // INSERT depends on member com
|
||||||
|
{ type: "user", orderOnInsert: 1, orderOnClear: 1 },
|
||||||
|
{ type: "webapi", orderOnInsert: 1, orderOnClear: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly backupSectionRefered: BackupSectionRefered = {
|
||||||
|
member: [
|
||||||
|
"member",
|
||||||
|
"member_awards",
|
||||||
|
"member_qualifications",
|
||||||
|
"member_executive_positions",
|
||||||
|
"membership",
|
||||||
|
"communication",
|
||||||
|
],
|
||||||
|
memberBase: [
|
||||||
|
"award",
|
||||||
|
"qualification",
|
||||||
|
"executive_position",
|
||||||
|
"membership_status",
|
||||||
|
"communication_type",
|
||||||
|
"salutation",
|
||||||
|
],
|
||||||
|
protocol: [
|
||||||
|
"protocol",
|
||||||
|
"protocol_agenda",
|
||||||
|
"protocol_decision",
|
||||||
|
"protocol_presence",
|
||||||
|
"protocol_printout",
|
||||||
|
"protocol_voting",
|
||||||
|
],
|
||||||
|
newsletter: ["newsletter", "newsletter_dates", "newsletter_recipients", "newsletter_config"],
|
||||||
|
newsletter_config: ["newsletter_config"],
|
||||||
|
calendar: ["calendar", "calendar_type"],
|
||||||
|
query: ["query"],
|
||||||
|
template: ["template", "template_usage"],
|
||||||
|
user: ["user", "user_permission", "role", "role_permission", "invite"],
|
||||||
|
webapi: ["webapi", "webapi_permission"],
|
||||||
|
};
|
||||||
|
|
||||||
|
private static transactionManager: EntityManager;
|
||||||
|
|
||||||
|
static async createBackup({
|
||||||
|
filename,
|
||||||
|
path = "/backup",
|
||||||
|
collectIds = true,
|
||||||
|
}: {
|
||||||
|
filename?: string;
|
||||||
|
path?: string;
|
||||||
|
collectIds?: boolean;
|
||||||
|
}): Promise<void> {
|
||||||
|
if (!filename) {
|
||||||
|
filename = new Date().toISOString().split("T")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
let json: BackupFileContent = { collectIds };
|
||||||
|
for (const section of this.backupSection) {
|
||||||
|
json[section.type] = await this.getSectionData(section.type, collectIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSystemHelper.writeFile(path, filename + ".json", JSON.stringify(json, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async loadBackup({
|
||||||
|
filename,
|
||||||
|
path = "/backup",
|
||||||
|
include = [],
|
||||||
|
partial = false,
|
||||||
|
}: {
|
||||||
|
filename: string;
|
||||||
|
path?: string;
|
||||||
|
partial?: boolean;
|
||||||
|
include?: Array<BackupSection>;
|
||||||
|
}): Promise<void> {
|
||||||
|
this.transactionManager = undefined;
|
||||||
|
|
||||||
|
let file = FileSystemHelper.readFile(`${path}/${filename}`);
|
||||||
|
let backup: BackupFileContent = JSON.parse(file);
|
||||||
|
|
||||||
|
dataSource.manager
|
||||||
|
.transaction(async (transaction) => {
|
||||||
|
this.transactionManager = transaction;
|
||||||
|
|
||||||
|
const sections = this.backupSection
|
||||||
|
.filter((bs) => (partial ? include.includes(bs.type) : true))
|
||||||
|
.sort((a, b) => a.orderOnClear - b.orderOnClear);
|
||||||
|
for (const section of sections.filter((s) => Object.keys(backup).includes(s.type))) {
|
||||||
|
let refered = this.backupSectionRefered[section.type];
|
||||||
|
for (const ref of refered) {
|
||||||
|
await this.transactionManager.getRepository(ref).delete({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const section of sections
|
||||||
|
.filter((s) => Object.keys(backup).includes(s.type))
|
||||||
|
.sort((a, b) => a.orderOnInsert - b.orderOnInsert)) {
|
||||||
|
await this.setSectionData(section.type, backup[section.type], backup.collectIds ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.transactionManager = undefined;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.transactionManager = undefined;
|
||||||
|
throw new InternalException("failed to restore backup - rolling back actions");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async autoRestoreBackup() {
|
||||||
|
let count = await UserService.count();
|
||||||
|
if (count == 0) {
|
||||||
|
let files = FileSystemHelper.getFilesInDirectory("/backup", ".json");
|
||||||
|
let newestFile = files.sort(
|
||||||
|
(a, b) => new Date(b.split(".")[0]).getTime() - new Date(a.split(".")[0]).getTime()
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
if (!newestFile) {
|
||||||
|
console.log(`${new Date().toISOString()}: auto-restoring ${newestFile}`);
|
||||||
|
await this.loadBackup({ filename: newestFile });
|
||||||
|
console.log(`${new Date().toISOString()}: finished auto-restore`);
|
||||||
|
} else {
|
||||||
|
console.log(`${new Date().toISOString()}: skip auto-restore as no backup was found`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`${new Date().toISOString()}: skip auto-restore as users exist`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async getSectionData(
|
||||||
|
section: BackupSection,
|
||||||
|
collectIds: boolean
|
||||||
|
): Promise<Array<any> | { [key: string]: any }> {
|
||||||
|
switch (section) {
|
||||||
|
case "member":
|
||||||
|
return await this.getMemberData(collectIds);
|
||||||
|
case "memberBase":
|
||||||
|
return await this.getMemberBase();
|
||||||
|
case "protocol":
|
||||||
|
return await this.getProtocol(collectIds);
|
||||||
|
case "newsletter":
|
||||||
|
return await this.getNewsletter(collectIds);
|
||||||
|
case "newsletter_config":
|
||||||
|
return await this.getNewsletterConfig();
|
||||||
|
case "calendar":
|
||||||
|
return await this.getCalendar();
|
||||||
|
case "query":
|
||||||
|
return await this.getQueryStore();
|
||||||
|
case "template":
|
||||||
|
return await this.getTemplate();
|
||||||
|
case "user":
|
||||||
|
return await this.getUser(collectIds);
|
||||||
|
case "webapi":
|
||||||
|
return await this.getWebapi();
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async getMemberData(collectIds: boolean): Promise<Array<any>> {
|
||||||
|
return await dataSource
|
||||||
|
.getRepository("member")
|
||||||
|
.createQueryBuilder("member")
|
||||||
|
.leftJoin("member.salutation", "salutation")
|
||||||
|
.leftJoin("member.communications", "communication")
|
||||||
|
.leftJoin("communication.type", "communicationType")
|
||||||
|
.leftJoin("member.memberships", "memberships")
|
||||||
|
.leftJoin("memberships.status", "membershipStatus")
|
||||||
|
.leftJoin("member.awards", "awards")
|
||||||
|
.leftJoin("awards.award", "award")
|
||||||
|
.leftJoin("member.positions", "positions")
|
||||||
|
.leftJoin("positions.executivePosition", "executivePosition")
|
||||||
|
.leftJoin("member.qualifications", "qualifications")
|
||||||
|
.leftJoin("qualifications.qualification", "qualification")
|
||||||
|
.select([
|
||||||
|
...(collectIds ? ["member.id"] : []),
|
||||||
|
"member.firstname",
|
||||||
|
"member.lastname",
|
||||||
|
"member.nameaffix",
|
||||||
|
"member.birthdate",
|
||||||
|
"member.internalId",
|
||||||
|
])
|
||||||
|
.addSelect(["salutation.salutation"])
|
||||||
|
.addSelect([
|
||||||
|
"communication.preferred",
|
||||||
|
"communication.isSMSAlarming",
|
||||||
|
"communication.isSendNewsletter",
|
||||||
|
"communication.mobile",
|
||||||
|
"communication.email",
|
||||||
|
"communication.postalCode",
|
||||||
|
"communication.city",
|
||||||
|
"communication.street",
|
||||||
|
"communication.streetNumber",
|
||||||
|
"communication.streetNumberAddition",
|
||||||
|
"communicationType.type",
|
||||||
|
"communicationType.useColumns",
|
||||||
|
])
|
||||||
|
.addSelect(["memberships.start", "memberships.end", "memberships.terminationReason", "membershipStatus.status"])
|
||||||
|
.addSelect(["awards.given", "awards.note", "awards.note", "awards.date", "award.award"])
|
||||||
|
.addSelect(["positions.note", "positions.start", "positions.end", "executivePosition.position"])
|
||||||
|
.addSelect([
|
||||||
|
"qualifications.note",
|
||||||
|
"qualifications.start",
|
||||||
|
"qualifications.end",
|
||||||
|
"qualifications.terminationReason",
|
||||||
|
"qualification.qualification",
|
||||||
|
"qualification.description",
|
||||||
|
])
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
private static async getMemberBase(): Promise<{ [key: string]: Array<any> }> {
|
||||||
|
return {
|
||||||
|
award: await dataSource.getRepository("award").find({ select: { award: true } }),
|
||||||
|
communication_type: await dataSource
|
||||||
|
.getRepository("communication_type")
|
||||||
|
.find({ select: { type: true, useColumns: true } }),
|
||||||
|
executive_position: await dataSource.getRepository("executive_position").find({ select: { position: true } }),
|
||||||
|
membership_status: await dataSource.getRepository("membership_status").find({ select: { status: true } }),
|
||||||
|
salutation: await dataSource.getRepository("salutation").find({ select: { salutation: true } }),
|
||||||
|
qualification: await dataSource
|
||||||
|
.getRepository("qualification")
|
||||||
|
.find({ select: { qualification: true, description: true } }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private static async getProtocol(collectIds: boolean): Promise<Array<any>> {
|
||||||
|
return await dataSource
|
||||||
|
.getRepository("protocol")
|
||||||
|
.createQueryBuilder("protocol")
|
||||||
|
.leftJoin("protocol.agendas", "agendas")
|
||||||
|
.leftJoin("protocol.decisions", "decisions")
|
||||||
|
.leftJoin("protocol.presences", "presences")
|
||||||
|
.leftJoin("presences.member", "member")
|
||||||
|
.leftJoin("protocol.printouts", "printouts")
|
||||||
|
.leftJoin("protocol.votings", "votings")
|
||||||
|
.select(["protocol.title", "protocol.date", "protocol.starttime", "protocol.endtime", "protocol.summary"])
|
||||||
|
.addSelect(["agendas.topic", "agendas.context"])
|
||||||
|
.addSelect(["decisions.topic", "decisions.context"])
|
||||||
|
.addSelect(["presences.absent", "presences.excused"])
|
||||||
|
.addSelect([
|
||||||
|
...(collectIds ? ["member.id"] : []),
|
||||||
|
"member.firstname",
|
||||||
|
"member.lastname",
|
||||||
|
"member.nameaffix",
|
||||||
|
"member.birthdate",
|
||||||
|
"member.internalId",
|
||||||
|
])
|
||||||
|
.addSelect(["printouts.title", "printouts.iteration", "printouts.filename", "printouts.createdAt"])
|
||||||
|
.addSelect(["votings.topic", "votings.context", "votings.favour", "votings.abstain", "votings.against"])
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
private static async getNewsletter(collectIds: boolean): Promise<Array<any>> {
|
||||||
|
return await dataSource
|
||||||
|
.getRepository("newsletter")
|
||||||
|
.createQueryBuilder("newsletter")
|
||||||
|
.leftJoin("newsletter.dates", "dates")
|
||||||
|
.leftJoin("newsletter.recipients", "recipients")
|
||||||
|
.leftJoin("recipients.member", "member")
|
||||||
|
.leftJoin("newsletter.recipientsByQuery", "recipientsByQuery")
|
||||||
|
.select([
|
||||||
|
"newsletter.title",
|
||||||
|
"newsletter.description",
|
||||||
|
"newsletter.newsletterTitle",
|
||||||
|
"newsletter.newsletterText",
|
||||||
|
"newsletter.newsletterSignatur",
|
||||||
|
"newsletter.isSent",
|
||||||
|
])
|
||||||
|
.addSelect(["dates.calendarId", "dates.diffTitle", "dates.diffDescription"])
|
||||||
|
.addSelect([
|
||||||
|
...(collectIds ? ["member.id"] : []),
|
||||||
|
"member.firstname",
|
||||||
|
"member.lastname",
|
||||||
|
"member.nameaffix",
|
||||||
|
"member.birthdate",
|
||||||
|
"member.internalId",
|
||||||
|
])
|
||||||
|
.addSelect(["recipientsByQuery.title", "recipientsByQuery.query"])
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
private static async getNewsletterConfig(): Promise<Array<any>> {
|
||||||
|
return await dataSource
|
||||||
|
.getRepository("newsletter_config")
|
||||||
|
.createQueryBuilder("newsletter_config")
|
||||||
|
.leftJoin("newsletter_config.comType", "comType")
|
||||||
|
.select(["newsletter_config.config"])
|
||||||
|
.addSelect(["comType.type", "comType.useColumns"])
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
private static async getCalendar(): Promise<{ [key: string]: Array<any> }> {
|
||||||
|
return {
|
||||||
|
calendar: await dataSource
|
||||||
|
.getRepository("calendar")
|
||||||
|
.createQueryBuilder("calendar")
|
||||||
|
.leftJoin("calendar.type", "type")
|
||||||
|
.select([
|
||||||
|
"calendar.id",
|
||||||
|
"calendar.starttime",
|
||||||
|
"calendar.endtime",
|
||||||
|
"calendar.title",
|
||||||
|
"calendar.content",
|
||||||
|
"calendar.location",
|
||||||
|
"calendar.allDay",
|
||||||
|
"calendar.sequence",
|
||||||
|
"calendar.createdAt",
|
||||||
|
"calendar.updatedAt",
|
||||||
|
])
|
||||||
|
.addSelect(["type.type", "type.nscdr", "type.color", "type.passphrase"])
|
||||||
|
.getMany(),
|
||||||
|
calendar_type: await dataSource
|
||||||
|
.getRepository("calendar_type")
|
||||||
|
.createQueryBuilder("calendar_type")
|
||||||
|
.select(["calendar_type.type", "calendar_type.nscdr", "calendar_type.color", "calendar_type.passphrase"])
|
||||||
|
.getMany(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private static async getQueryStore(): Promise<Array<any>> {
|
||||||
|
return await dataSource.getRepository("query").find({ select: { title: true, query: true } });
|
||||||
|
}
|
||||||
|
private static async getTemplate(): Promise<{ [key: string]: Array<any> }> {
|
||||||
|
return {
|
||||||
|
template: await dataSource
|
||||||
|
.getRepository("template")
|
||||||
|
.find({ select: { template: true, description: true, design: true, html: true } }),
|
||||||
|
template_usage: await dataSource
|
||||||
|
.getRepository("template_usage")
|
||||||
|
.createQueryBuilder("template_usage")
|
||||||
|
.leftJoin("template_usage.header", "header")
|
||||||
|
.leftJoin("template_usage.body", "body")
|
||||||
|
.leftJoin("template_usage.footer", "footer")
|
||||||
|
.select(["template_usage.scope", "template_usage.headerHeight", "template_usage.footerHeight"])
|
||||||
|
.addSelect(["header.template", "header.description", "header.design", "header.html"])
|
||||||
|
.addSelect(["body.template", "body.description", "body.design", "body.html"])
|
||||||
|
.addSelect(["footer.template", "footer.description", "footer.design", "footer.html"])
|
||||||
|
.getMany(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private static async getUser(collectIds: boolean): Promise<{ [key: string]: Array<any> }> {
|
||||||
|
return {
|
||||||
|
user: await dataSource
|
||||||
|
.getRepository("user")
|
||||||
|
.createQueryBuilder("user")
|
||||||
|
.leftJoin("user.roles", "roles")
|
||||||
|
.leftJoin("roles.permissions", "role_permissions")
|
||||||
|
.leftJoin("user.permissions", "permissions")
|
||||||
|
.select([
|
||||||
|
...(collectIds ? ["user.id"] : []),
|
||||||
|
"user.mail",
|
||||||
|
"user.username",
|
||||||
|
"user.firstname",
|
||||||
|
"user.lastname",
|
||||||
|
"user.secret",
|
||||||
|
"user.isOwner",
|
||||||
|
])
|
||||||
|
.addSelect(["permissions.permission"])
|
||||||
|
.addSelect(["roles.role"])
|
||||||
|
.addSelect(["role_permissions.permission"])
|
||||||
|
.getMany(),
|
||||||
|
role: await dataSource
|
||||||
|
.getRepository("role")
|
||||||
|
.createQueryBuilder("role")
|
||||||
|
.leftJoin("role.permissions", "permissions")
|
||||||
|
.addSelect(["role.role"])
|
||||||
|
.addSelect(["permissions.permission"])
|
||||||
|
.getMany(),
|
||||||
|
invite: await dataSource.getRepository("invite").find(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private static async getWebapi(): Promise<Array<any>> {
|
||||||
|
return await dataSource
|
||||||
|
.getRepository("webapi")
|
||||||
|
.createQueryBuilder("webapi")
|
||||||
|
.leftJoin("webapi.permissions", "permissions")
|
||||||
|
.select(["webapi.token", "webapi.title", "webapi.createdAt", "webapi.lastUsage", "webapi.expiry"])
|
||||||
|
.addSelect(["permissions.permission"])
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async setSectionData(
|
||||||
|
section: BackupSection,
|
||||||
|
data: BackupFileContentSection,
|
||||||
|
collectedIds: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
if (section == "member" && Array.isArray(data)) await this.setMemberData(data);
|
||||||
|
if (section == "memberBase" && !Array.isArray(data)) await this.setMemberBase(data);
|
||||||
|
if (section == "protocol" && Array.isArray(data)) await this.setProtocol(data, collectedIds);
|
||||||
|
if (section == "newsletter" && Array.isArray(data)) await this.setNewsletter(data, collectedIds);
|
||||||
|
if (section == "newsletter_config" && Array.isArray(data)) await this.setNewsletterConfig(data);
|
||||||
|
if (section == "calendar" && !Array.isArray(data)) await this.setCalendar(data);
|
||||||
|
if (section == "query" && Array.isArray(data)) await this.setQueryStore(data);
|
||||||
|
if (section == "template" && !Array.isArray(data)) await this.setTemplate(data);
|
||||||
|
if (section == "user" && !Array.isArray(data)) await this.setUser(data);
|
||||||
|
if (section == "webapi" && Array.isArray(data)) await this.setWebapi(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async setMemberData(data: Array<any>): Promise<void> {
|
||||||
|
let salutation = await this.transactionManager.getRepository("salutation").find();
|
||||||
|
let communication = await this.transactionManager.getRepository("communication_type").find();
|
||||||
|
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 position = await this.transactionManager.getRepository("executive_position").find();
|
||||||
|
let dataWithMappedIds = data.map((d) => ({
|
||||||
|
...d,
|
||||||
|
salutation: {
|
||||||
|
...d.salutation,
|
||||||
|
id: salutation.find((s) => s.salutation == d.salutation.salutation)?.id ?? undefined,
|
||||||
|
},
|
||||||
|
communications: d.communications.map((c: any) => ({
|
||||||
|
...c,
|
||||||
|
type: {
|
||||||
|
...c.type,
|
||||||
|
id: communication.find((s) => s.type == c.type.type)?.id ?? undefined,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
memberships: d.memberships.map((m: any) => ({
|
||||||
|
...m,
|
||||||
|
status: {
|
||||||
|
...m.status,
|
||||||
|
id: membership.find((ms) => ms.status == m.status.status)?.id ?? undefined,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
awards: d.awards.map((a: any) => ({
|
||||||
|
...a,
|
||||||
|
award: {
|
||||||
|
...a.award,
|
||||||
|
id: award.find((ia) => ia.award == a.award.award)?.id ?? undefined,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
positions: d.positions.map((p: any) => ({
|
||||||
|
...p,
|
||||||
|
executivePosition: {
|
||||||
|
...p.executivePosition,
|
||||||
|
id: position.find((ip) => ip.position == p.executivePosition.position)?.id ?? undefined,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
qualifications: d.qualifications.map((q: any) => ({
|
||||||
|
...q,
|
||||||
|
qualification: {
|
||||||
|
...q.qualification,
|
||||||
|
id: qualification.find((iq) => iq.qualification == q.qualification.qualification)?.id ?? undefined,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
await this.transactionManager.getRepository("member").save(dataWithMappedIds);
|
||||||
|
}
|
||||||
|
private static async setMemberBase(data: { [key: string]: Array<any> }): Promise<void> {
|
||||||
|
await this.transactionManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("award")
|
||||||
|
.values(data["award"])
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
await this.transactionManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("communication_type")
|
||||||
|
.values(data["communication_type"])
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
await this.transactionManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("executive_position")
|
||||||
|
.values(data["executive_position"])
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
await this.transactionManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("membership_status")
|
||||||
|
.values(data["membership_status"])
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
await this.transactionManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("salutation")
|
||||||
|
.values(data["salutation"])
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
await this.transactionManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("qualification")
|
||||||
|
.values(data["qualification"])
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
private static async setProtocol(data: Array<any>, collectedIds: boolean): Promise<void> {
|
||||||
|
let members = await this.transactionManager.getRepository("member").find();
|
||||||
|
let dataWithMappedIds = data.map((d) => ({
|
||||||
|
...d,
|
||||||
|
...(!collectedIds
|
||||||
|
? {
|
||||||
|
presences: d.presences.map((p: any) => ({
|
||||||
|
...p,
|
||||||
|
memberId:
|
||||||
|
members.find(
|
||||||
|
(m) =>
|
||||||
|
m.firstname == p.member.firstname &&
|
||||||
|
m.lastname == p.member.lastname &&
|
||||||
|
m.nameaffix == p.member.nameaffix &&
|
||||||
|
m.birthdate == p.member.birthdate &&
|
||||||
|
m.internalId == p.member.internalId
|
||||||
|
)?.id ?? undefined,
|
||||||
|
member: null,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}));
|
||||||
|
await this.transactionManager.getRepository("protocol").save(dataWithMappedIds);
|
||||||
|
}
|
||||||
|
private static async setNewsletter(data: Array<any>, collectedIds: boolean): Promise<void> {
|
||||||
|
let queries = await this.transactionManager.getRepository("query").find();
|
||||||
|
let members = await this.transactionManager.getRepository("member").find();
|
||||||
|
let dataWithMappedIds = data.map((d) => ({
|
||||||
|
...d,
|
||||||
|
recipientsByQueryId: {
|
||||||
|
...d.recipientsByQueryId,
|
||||||
|
id: queries.find((s) => s.query == d.recipientsByQueryId.query)?.id ?? undefined,
|
||||||
|
},
|
||||||
|
...(!collectedIds
|
||||||
|
? {
|
||||||
|
recipients: d.recipients.map((r: any) => ({
|
||||||
|
...r,
|
||||||
|
memberId:
|
||||||
|
members.find(
|
||||||
|
(m) =>
|
||||||
|
m.firstname == r.member.firstname &&
|
||||||
|
m.lastname == r.member.lastname &&
|
||||||
|
m.nameaffix == r.member.nameaffix &&
|
||||||
|
m.birthdate == r.member.birthdate &&
|
||||||
|
m.internalId == r.member.internalId
|
||||||
|
)?.id ?? undefined,
|
||||||
|
member: null,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}));
|
||||||
|
await this.transactionManager.getRepository("newsletter").save(dataWithMappedIds);
|
||||||
|
}
|
||||||
|
private static async setNewsletterConfig(data: Array<any>): Promise<void> {
|
||||||
|
let types = await this.transactionManager.getRepository("communication_type").find();
|
||||||
|
let dataWithMappedIds = data.map((d) => ({
|
||||||
|
...d,
|
||||||
|
comType: {
|
||||||
|
...d.comType,
|
||||||
|
id: types.find((type) => type.type == d.comType.type)?.id ?? undefined,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
await this.transactionManager.getRepository("newsletter_config").save(dataWithMappedIds);
|
||||||
|
}
|
||||||
|
private static async setCalendar(data: { [key: string]: Array<any> }): Promise<void> {
|
||||||
|
await this.transactionManager.getRepository("calendar_type").save(data["calendar_type"]);
|
||||||
|
let types = await this.transactionManager.getRepository("calendar_type").find();
|
||||||
|
let dataWithMappedIds = data["calendar"].map((c) => ({
|
||||||
|
...c,
|
||||||
|
type: {
|
||||||
|
...c.type,
|
||||||
|
id: types.find((type) => type.type == c.type.type)?.id ?? undefined,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
await this.transactionManager.getRepository("calendar").save(dataWithMappedIds);
|
||||||
|
}
|
||||||
|
private static async setQueryStore(data: Array<any>): Promise<void> {
|
||||||
|
await this.transactionManager.createQueryBuilder().insert().into("query").values(data).orIgnore().execute();
|
||||||
|
}
|
||||||
|
private static async setTemplate(data: { [key: string]: Array<any> }): Promise<void> {
|
||||||
|
await this.transactionManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("template")
|
||||||
|
.values(data["template"])
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
await this.transactionManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("template_usage")
|
||||||
|
.values(data["template_usage"])
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
private static async setUser(data: { [key: string]: Array<any> }): Promise<void> {
|
||||||
|
await this.transactionManager.createQueryBuilder().insert().into("role").values(data["role"]).orIgnore().execute();
|
||||||
|
let roles = await this.transactionManager.getRepository("role").find();
|
||||||
|
let dataWithMappedIds = data["user"].map((u) => ({
|
||||||
|
...u,
|
||||||
|
roles: u.roles.map((r: any) => ({
|
||||||
|
...r,
|
||||||
|
id: roles.find((role) => role.role == r.role)?.id ?? undefined,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
await this.transactionManager.getRepository("user").save(dataWithMappedIds);
|
||||||
|
await this.transactionManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("invite")
|
||||||
|
.values(data["invite"])
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
private static async setWebapi(data: Array<any>): Promise<void> {
|
||||||
|
await this.transactionManager.getRepository("webapi").save(data);
|
||||||
|
}
|
||||||
|
}
|
15
src/index.ts
15
src/index.ts
|
@ -1,7 +1,7 @@
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
|
||||||
import { configCheck, SERVER_PORT } from "./env.defaults";
|
import { BACKUP_AUTO_RESTORE, configCheck, SERVER_PORT } from "./env.defaults";
|
||||||
configCheck();
|
configCheck();
|
||||||
|
|
||||||
import { PermissionObject } from "./type/permissionTypes";
|
import { PermissionObject } from "./type/permissionTypes";
|
||||||
|
@ -19,18 +19,25 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
import { dataSource } from "./data-source";
|
import { dataSource } from "./data-source";
|
||||||
dataSource.initialize();
|
import BackupHelper from "./helpers/backupHelper";
|
||||||
|
dataSource.initialize().then(async () => {
|
||||||
|
if ((BACKUP_AUTO_RESTORE as "true" | "false") == "true") {
|
||||||
|
await BackupHelper.autoRestoreBackup().catch((err) => {
|
||||||
|
console.log(`${new Date().toISOString()}: failed auto-restoring database`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
import router from "./routes/index";
|
import router from "./routes/index";
|
||||||
router(app);
|
router(app);
|
||||||
app.listen(process.env.NODE_ENV ? SERVER_PORT : 5000, () => {
|
app.listen(process.env.NODE_ENV ? SERVER_PORT : 5000, () => {
|
||||||
console.log(`listening on *:${process.env.NODE_ENV ? SERVER_PORT : 5000}`);
|
console.log(`${new Date().toISOString()}: listening on port ${process.env.NODE_ENV ? SERVER_PORT : 5000}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
import schedule from "node-schedule";
|
import schedule from "node-schedule";
|
||||||
import RefreshCommandHandler from "./command/refreshCommandHandler";
|
import RefreshCommandHandler from "./command/refreshCommandHandler";
|
||||||
const job = schedule.scheduleJob("0 0 * * *", async () => {
|
const job = schedule.scheduleJob("0 0 * * *", async () => {
|
||||||
console.log(`running Cron at ${new Date()}`);
|
console.log(`${new Date().toISOString()}: running Cron`);
|
||||||
await RefreshCommandHandler.deleteExpired();
|
await RefreshCommandHandler.deleteExpired();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue