async function getAccessToken() {
const auth = await fetch('https://storeganise.sensorberg.io/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `grant_type=client_credentials&client_id=${process.env.CLIENT_ID}&client_secret=${process.env.CLIENT_SECRET}&scope=app`,
}).then(r => r.json());
return auth.access_token;
}
async function rent({ unit, owner, rental, fetchSg, fetchSensorberg }) {
let user;
try {
user = await fetchSensorberg(`users/${owner.id}`);
} catch (err) {
if (err.status !== 404) throw err;
user = await fetchSensorberg('users', {
method: 'POST',
data: {
type: 'users',
attributes: {
'full-name': owner.name,
email: owner.email,
'external-identifier': owner.id,
locale: owner.language,
}
}
}).catch(err => {
if (err.status !== 422) throw err;
console.log('User already exists, skip', err.message);
});
}
const gm = await fetchSensorberg('user_group_memberships', {
method: 'POST',
data: {
type: 'user-group-memberships',
attributes: {
'external-identifier': rental.id,
'external-user-identifier': owner.id,
'external-user-group-identifier': unit.customFields.sensorberg_id,
'created-at': new Date().toJSON().replace(/\.\d*/, ''),
'starts-at': new Date(rental.startDate).toJSON().replace(/\.\d*/, ''),
// 'ends-at': '2099-12-31T00:00:00Z'
}
}
});
if (!owner.customFields.sensorberg_pin) {
owner.customFields.sensorberg_pin = `${Math.floor(1_000_000 * Math.random())}`.padStart(4, 0);
await fetchSg(`users/${owner.id}`, { method: 'PUT', body: { customFields: { sensorberg_pin: owner.customFields.sensorberg_pin } } });
await setAccessCode({ owner });
}
await fetchSg(`unit-rentals/${rental.id}`, { method: 'PUT', body: { customFields: { sensorberg_pin: owner.customFields.sensorberg_pin } } });
}
async function vacate({ rental, fetchSensorberg }) {
return fetchSensorberg(`user_group_memberships/${rental.id}`, { method: 'DELETE' });
}
async function lockOut({ rental, overlocked = rental.overdue, fetchSg, fetchSensorberg }) {
await fetchSensorberg(`user_group_memberships/${rental.id}`, {
method: 'PUT',
data: {
type: 'user-group-memberships',
attributes: {
'ends-at': new Date(overlocked || '2099-12-31T23:59:59Z').toJSON().replace(/\.\d*/, ''),
}
}
});
await fetchSg(`unit-rentals/${rental.id}`, { method: 'PUT', body: { customFields: { sensorberg_overlocked: !!overlocked } } });
}
async function setAccessCode({ owner, pin = owner.customFields.sensorberg_pin, fetchSg, fetchSensorberg }) {
await fetchSensorberg(`users/${owner.id}/identification_code`, {
method: 'PUT',
data: {
type: 'users',
attributes: {
'identification-code': pin
}
}
});
if (pin !== owner.customFields.sensorberg_pin) {
await fetchSg(`users/${owner.id}`, { method: 'PUT', body: { customFields: { sensorberg_pin: pin } } });
}
const rentals = await fetchSg(`unit-rentals?ownerId=${owner.id}&state=occupied`);
await Promise.all(
rentals.map(r => fetchSg(`unit-rentals/${r.id}`, { method: 'PUT', body: { customFields: { sensorberg_pin: pin } } }))
);
}
export default async (req, res) => {
const { businessCode } = req.query || {};
const { data } = req.body || {};
if (!businessCode) return res.json({ message: 'missing businessCode' });
if (!process.env[`API_KEY_${businessCode}`]) return res.json({ message: 'missing API_KEY for ${businessCode}' });
async function fetchSg(path, opts = {}) {
return fetch(`https://${businessCode}.storeganise.com/api/v1/admin/${path}`, {
method: opts.method,
headers: {
Authorization: `ApiKey ${process.env[`API_KEY_${businessCode}`]}`,
...opts.body && { 'Content-Type': 'application/json' },
},
body: opts.body && JSON.stringify(opts.body),
})
.then(async r => {
const data = await r.json().catch(() => ({}));
if (r.ok) return data;
const err = Object.assign(new Error(), data.error);
err.status = r.status;
throw err;
});
}
const job = await fetchSg(`jobs/${data.jobId}`).catch(() => null);
const site = job && await fetchSg(`sites/${job.result.siteId}?include=customFields`).catch(() => null);
if (!site) return res.json({ message: 'missing job/site' });
// When the middleware is used as an addon, use this instead:
// const addon = await fetchSg(`addons/${data.addonId}`);
// then use credentials stored in addon.customFields instead of site.customFields
const accessToken = await getAccessToken();
async function fetchSensorberg(path, { method = 'GET', data } = {}) {
return fetch(`https://storeganise.sensorberg.io/api/backend-sdk/v1/${path}`, {
method,
headers: {
'content-type': 'application/vnd.api+json',
'authorization': `Bearer ${accessToken}`,
},
body: data && JSON.stringify({ data }),
})
.then(async r => {
return r.json().then(result => {
if (!r.ok) {
throw Object.assign(new Error(JSON.stringify(result.errors || result)), { status: r.status, method, path });
}
return result.data;
});
});
}
try {
switch (req.body.type) {
case 'job.unit_moveIn.completed': {
const rental = await fetchSg(`unit-rentals/${data.unitRentalId}?include=unit,owner,customFields`);
await rent({ unit: rental.unit, owner: rental.owner, rental, fetchSg, fetchSensorberg });
break;
}
case 'job.unit_moveOut.completed': {
const rental = await fetchSg(`unit-rentals/${data.unitRentalId}?include=customFields`);
await vacate({ rental, fetchSensorberg });
break;
}
case 'job.unit_transfer.completed': {
const oldRental = await fetchSg(`unit-rentals/${data.oldRentalId}?include=unit,customFields`);
const newRental = await fetchSg(`unit-rentals/${data.newRentalId}?include=unit,owner,customFields`);
await vacate({ rental: oldRental, fetchSensorberg });
await rent({ unit: newRental.unit, owner: newRental.owner, rental: newRental, fetchSg, fetchSensorberg });
break;
}
case 'unitRental.updated': {
const rental = await fetchSg(`unit-rentals/${data.unitRentalId}?include=owner,customFields`);
if (data.changedKeys?.includes('customFields.sensorberg_pin')) {
await setAccessCode({ owner: rental.owner, pin: rental.customFields.sensorberg_pin, fetchSg, fetchSensorberg });
}
if (data.changedKeys?.includes('customFields.sensorberg_overlocked')) {
await lockOut({ rental, overlocked: rental.customFields.sensorberg_overlocked ? new Date() : null, fetchSg, fetchSensorberg });
}
break;
}
case 'unitRental.markOverdue':
case 'unitRental.unmarkOverdue': {
await fetchSg(`unit-rentals?include=unit,customFields&ids=${data.unitRentalIds || data .unitRentalId}`)
.then(rentals => Promise.all(
rentals.map(rental => {
return lockOut({ rental, fetchSg, fetchSensorberg });
})
));
break;
}
}
res.json({ message: 'ok' });
} catch(err) {
console.error(err);
res.status(400).json({ message: err.message });
}
}