Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const webhook = require('./webhook');
const associatedMovementsTriggers = require('./associatedMovements/setAssociatedMovementsTriggers');
const invoiceRecipientsTrigger = require('./invoiceRecipients/invoiceRecipientsTrigger');
const updateArrivalPaymentStatus = require('./updateArrivalPaymentStatus');
const movementAudit = require('./movementAudit');

exports.auth = auth;
exports.generateSignInLink = generateSignInLink;
Expand All @@ -47,3 +48,8 @@ exports.enrichArrivalOnCreate = enrichMovements.enrichArrivalOnCreate;
exports.enrichArrivalOnUpdate = enrichMovements.enrichArrivalOnUpdate;

exports.updateArrivalPaymentStatusOnCardPaymentUpdate = updateArrivalPaymentStatus.updateArrivalPaymentStatusOnCardPaymentUpdate;

exports.auditDepartureOnCreate = movementAudit.auditDepartureOnCreate;
exports.auditArrivalOnCreate = movementAudit.auditArrivalOnCreate;
exports.auditDepartureOnWrite = movementAudit.auditDepartureOnWrite;
exports.auditArrivalOnWrite = movementAudit.auditArrivalOnWrite;
105 changes: 105 additions & 0 deletions functions/movementAudit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const functions = require('firebase-functions');
const admin = require('firebase-admin');

const AUDIT_FIELDS = ['createdBy', 'createdByName', 'createdAt', 'createdBy_orderKey', 'updatedBy', 'updatedByName', 'updatedAt'];

function stripAuditFields(obj) {
if (!obj) return obj;
const result = { ...obj };
AUDIT_FIELDS.forEach(field => delete result[field]);
return result;
}

function getCollectionPath(movementType) {
return movementType === 'departure' ? 'departures' : 'arrivals';
}

async function getUserDetails(uid) {
const userSnapshot = await admin.database()
.ref('users')
.child(uid)
.once('value');

if (!userSnapshot.exists()) {
return null;
}

return userSnapshot.val();
}

async function handleCreate(snapshot, context, movementType) {
if (context.authType !== 'USER') {
return null;
}

const uid = context.auth.uid;
const user = await getUserDetails(uid);
const movement = snapshot.val();

const auditData = {
createdAt: admin.database.ServerValue.TIMESTAMP,
};

if (user) {
auditData.createdBy = user.email || null;
auditData.createdByName = [user.firstname, user.lastname].filter(Boolean).join(' ') || null;
if (user.email && movement.negativeTimestamp) {
auditData.createdBy_orderKey = user.email + '_' + String(Math.abs(movement.negativeTimestamp));
}
}

return admin.database().ref(getCollectionPath(movementType)).child(snapshot.ref.key).update(auditData);
}

async function handleUpdate(change, context, movementType) {
if (context.authType !== 'USER') {
return null;
}

if (!change.before.exists() || !change.after.exists()) {
return null;
}

const before = stripAuditFields(change.before.val());
const after = stripAuditFields(change.after.val());

if (JSON.stringify(before) === JSON.stringify(after)) {
return null;
}

const uid = context.auth.uid;
const user = await getUserDetails(uid);

const auditData = {
updatedAt: admin.database.ServerValue.TIMESTAMP,
};

if (user) {
auditData.updatedBy = user.email || null;
auditData.updatedByName = [user.firstname, user.lastname].filter(Boolean).join(' ') || null;
}

return admin.database().ref(getCollectionPath(movementType)).child(change.after.ref.key).update(auditData);
}

const instance = functions.config().rtdb.instance;

exports.auditDepartureOnCreate = functions.region('europe-west1').database
.instance(instance)
.ref('/departures/{departureId}')
.onCreate((snapshot, context) => handleCreate(snapshot, context, 'departure'));

exports.auditArrivalOnCreate = functions.region('europe-west1').database
.instance(instance)
.ref('/arrivals/{arrivalId}')
.onCreate((snapshot, context) => handleCreate(snapshot, context, 'arrival'));

exports.auditDepartureOnWrite = functions.region('europe-west1').database
.instance(instance)
.ref('/departures/{departureId}')
.onWrite((change, context) => handleUpdate(change, context, 'departure'));

exports.auditArrivalOnWrite = functions.region('europe-west1').database
.instance(instance)
.ref('/arrivals/{arrivalId}')
.onWrite((change, context) => handleUpdate(change, context, 'arrival'));
Loading
Loading