2020-12-24 04:57:46 +00:00
|
|
|
import { clone } from "../utils/utils";
|
2023-09-12 11:31:55 +00:00
|
|
|
import { Model, DataTypes, FindOptions, InferAttributes, CreationOptional, ForeignKey, InferCreationAttributes } from "sequelize";
|
2020-04-14 23:40:00 +00:00
|
|
|
import Database from "./database";
|
2023-09-12 11:31:55 +00:00
|
|
|
import { HistoryEntry, HistoryEntryAction, HistoryEntryCreate, HistoryEntryType, ID, PadData, PadId } from "facilmap-types";
|
|
|
|
import { createModel, getDefaultIdType, makeNotNullForeignKey } from "./helpers";
|
|
|
|
|
|
|
|
interface HistoryModel extends Model<InferAttributes<HistoryModel>, InferCreationAttributes<HistoryModel>> {
|
|
|
|
id: CreationOptional<ID>;
|
|
|
|
time: Date;
|
|
|
|
type: HistoryEntryType;
|
|
|
|
action: HistoryEntryAction;
|
|
|
|
objectId: ID;
|
|
|
|
objectBefore: string | null;
|
|
|
|
objectAfter: string | null;
|
|
|
|
padId: ForeignKey<PadData["id"]>;
|
|
|
|
toJSON: () => HistoryEntry;
|
2020-04-14 23:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export default class DatabaseHistory {
|
|
|
|
|
|
|
|
HISTORY_ENTRIES = 50;
|
|
|
|
|
2023-09-12 11:31:55 +00:00
|
|
|
HistoryModel = createModel<HistoryModel>();
|
2020-04-14 23:40:00 +00:00
|
|
|
|
|
|
|
_db: Database;
|
|
|
|
|
|
|
|
constructor(database: Database) {
|
|
|
|
this._db = database;
|
|
|
|
|
|
|
|
this.HistoryModel.init({
|
2023-09-12 11:31:55 +00:00
|
|
|
id: getDefaultIdType(),
|
2020-04-14 23:40:00 +00:00
|
|
|
time: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
|
|
|
|
type: { type: DataTypes.ENUM("Marker", "Line", "View", "Type", "Pad"), allowNull: false },
|
|
|
|
action: { type: DataTypes.ENUM("create", "update", "delete"), allowNull: false },
|
|
|
|
objectId: { type: DataTypes.INTEGER(), allowNull: true }, // Is null when type is pad
|
|
|
|
objectBefore: {
|
|
|
|
type: DataTypes.TEXT,
|
|
|
|
allowNull: true,
|
|
|
|
get(this: HistoryModel) {
|
2020-12-24 17:51:21 +00:00
|
|
|
const obj = this.getDataValue("objectBefore");
|
2020-04-14 23:40:00 +00:00
|
|
|
return obj == null ? null : JSON.parse(obj);
|
|
|
|
},
|
|
|
|
set(this: HistoryModel, v) {
|
|
|
|
this.setDataValue("objectBefore", v == null ? null : JSON.stringify(v));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
objectAfter: {
|
|
|
|
type: DataTypes.TEXT,
|
|
|
|
allowNull: true,
|
|
|
|
get: function(this: HistoryModel) {
|
2020-12-24 17:51:21 +00:00
|
|
|
const obj = this.getDataValue("objectAfter");
|
2020-04-14 23:40:00 +00:00
|
|
|
return obj == null ? null : JSON.parse(obj);
|
|
|
|
},
|
|
|
|
set: function(this: HistoryModel, v) {
|
|
|
|
this.setDataValue("objectAfter", v == null ? null : JSON.stringify(v));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
sequelize: this._db._conn,
|
2020-12-25 13:21:09 +00:00
|
|
|
modelName: "History",
|
2020-04-14 23:40:00 +00:00
|
|
|
freezeTableName: true // Do not call it Histories
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-24 17:51:21 +00:00
|
|
|
afterInit(): void {
|
2020-12-24 04:57:46 +00:00
|
|
|
this._db.pads.PadModel.hasMany(this.HistoryModel, makeNotNullForeignKey("History", "padId"));
|
|
|
|
this.HistoryModel.belongsTo(this._db.pads.PadModel, makeNotNullForeignKey("pad", "padId"));
|
2020-04-14 23:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-24 17:51:21 +00:00
|
|
|
async addHistoryEntry(padId: PadId, data: HistoryEntryCreate): Promise<HistoryEntry> {
|
2020-12-24 04:57:46 +00:00
|
|
|
const oldEntryIds = (await this.HistoryModel.findAll({
|
|
|
|
where: { padId: padId },
|
|
|
|
order: [[ "time", "DESC" ]],
|
|
|
|
offset: this.HISTORY_ENTRIES-1,
|
|
|
|
attributes: [ "id" ]
|
|
|
|
})).map(it => it.id);
|
2020-04-14 23:40:00 +00:00
|
|
|
|
2020-12-24 04:57:46 +00:00
|
|
|
const dataClone = clone(data);
|
|
|
|
if(data.type != "Pad") {
|
|
|
|
if(dataClone.objectBefore) {
|
|
|
|
delete (dataClone.objectBefore as any).id;
|
|
|
|
delete (dataClone.objectBefore as any).padId;
|
|
|
|
}
|
|
|
|
if(dataClone.objectAfter) {
|
|
|
|
delete (dataClone.objectAfter as any).id;
|
|
|
|
delete (dataClone.objectAfter as any).padId;
|
2020-04-14 23:40:00 +00:00
|
|
|
}
|
2020-12-24 04:57:46 +00:00
|
|
|
}
|
2020-04-14 23:40:00 +00:00
|
|
|
|
2020-12-24 04:57:46 +00:00
|
|
|
const [newEntry] = await Promise.all([
|
|
|
|
this._db.helpers._createPadObject<HistoryEntry>("History", padId, dataClone),
|
|
|
|
oldEntryIds.length > 0 ? this.HistoryModel.destroy({ where: { padId: padId, id: oldEntryIds } }) : undefined
|
|
|
|
]);
|
|
|
|
|
|
|
|
this._db.emit("addHistoryEntry", padId, newEntry);
|
|
|
|
|
|
|
|
return newEntry;
|
2020-04-14 23:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-24 17:51:21 +00:00
|
|
|
getHistory(padId: PadId, types?: HistoryEntryType[]): Highland.Stream<HistoryEntry> {
|
|
|
|
const query: FindOptions = { order: [[ "time", "DESC" ]] };
|
2020-04-14 23:40:00 +00:00
|
|
|
if(types)
|
|
|
|
query.where = {type: types};
|
2020-12-24 04:57:46 +00:00
|
|
|
return this._db.helpers._getPadObjects<HistoryEntry>("History", padId, query);
|
2020-04-14 23:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-24 17:51:21 +00:00
|
|
|
async getHistoryEntry(padId: PadId, entryId: ID): Promise<HistoryEntry> {
|
2020-12-24 04:57:46 +00:00
|
|
|
return await this._db.helpers._getPadObject<HistoryEntry>("History", padId, entryId);
|
2020-04-14 23:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-24 17:51:21 +00:00
|
|
|
async revertHistoryEntry(padId: PadId, id: ID): Promise<void> {
|
2020-12-24 04:57:46 +00:00
|
|
|
const entry = await this.getHistoryEntry(padId, id);
|
2020-04-14 23:40:00 +00:00
|
|
|
|
2020-12-24 04:57:46 +00:00
|
|
|
if(entry.type == "Pad") {
|
|
|
|
if (!entry.objectBefore) {
|
|
|
|
throw new Error("Old pad data not available.");
|
|
|
|
}
|
|
|
|
await this._db.pads.updatePadData(padId, entry.objectBefore);
|
|
|
|
return;
|
|
|
|
} else if (!["Marker", "Line", "View", "Type"].includes(entry.type)) {
|
|
|
|
throw new Error(`Unknown type "${entry.type}.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const existsNow = await this._db.helpers._padObjectExists(entry.type, padId, entry.objectId);
|
|
|
|
|
|
|
|
if(entry.action == "create") {
|
|
|
|
if (!existsNow)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (entry.type) {
|
|
|
|
case "Marker":
|
|
|
|
await this._db.markers.deleteMarker(padId, entry.objectId);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Line":
|
|
|
|
await this._db.lines.deleteLine(padId, entry.objectId);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "View":
|
|
|
|
await this._db.views.deleteView(padId, entry.objectId);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Type":
|
|
|
|
await this._db.types.deleteType(padId, entry.objectId);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if(existsNow) {
|
|
|
|
switch (entry.type) {
|
|
|
|
case "Marker":
|
|
|
|
await this._db.markers.updateMarker(padId, entry.objectId, entry.objectBefore);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Line":
|
|
|
|
await this._db.lines.updateLine(padId, entry.objectId, entry.objectBefore);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "View":
|
|
|
|
await this._db.views.updateView(padId, entry.objectId, entry.objectBefore);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Type":
|
|
|
|
await this._db.types.updateType(padId, entry.objectId, entry.objectBefore);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let newObj;
|
2020-04-14 23:40:00 +00:00
|
|
|
|
2020-12-24 04:57:46 +00:00
|
|
|
switch (entry.type) {
|
|
|
|
case "Marker":
|
|
|
|
newObj = await this._db.markers.createMarker(padId, entry.objectBefore);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Line":
|
|
|
|
newObj = await this._db.lines.createLine(padId, entry.objectBefore);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "View":
|
|
|
|
newObj = await this._db.views.createView(padId, entry.objectBefore);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Type":
|
|
|
|
newObj = await this._db.types.createType(padId, entry.objectBefore);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.HistoryModel.update({ objectId: newObj.id }, { where: { padId: padId, type: entry.type, objectId: entry.objectId } });
|
|
|
|
this._db.emit("historyChange", padId);
|
|
|
|
}
|
2020-04-14 23:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-24 17:51:21 +00:00
|
|
|
async clearHistory(padId: PadId): Promise<void> {
|
2020-04-14 23:40:00 +00:00
|
|
|
await this.HistoryModel.destroy({ where: { padId: padId } });
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|