Refactor types and await for user/building backend (#476)

* Refactor buildings API for async/await, types

* Return building data after update

* Refactor users API for await, TS types

* Refactor building service to remove repetition

As part of this refactor, these changes in functionality were made:
- tx isolation lvl for save/like/unlike building is always serializable
- both reverse and forward patch updated for like/unlike
- comparing old and new data uses == instead of ===
 (this is because the new data even for numbers comes in as string)
- the checking of no data change in case of building unlike was fixed
 (didn't work because it re-used code for like which is different)

* Improve param order, docs for updateBuildingData
This commit is contained in:
mz8i 2019-10-21 15:19:35 +01:00 committed by GitHub
parent 921fcd16e4
commit c63f42f921
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 509 additions and 498 deletions

View File

@ -1,147 +1,139 @@
import express from 'express';
import * as buildingService from '../services/building';
import * as userService from '../services/user';
import asyncController from '../routes/asyncController';
// GET buildings
// not implemented - may be useful to GET all buildings, paginated
// GET buildings at point
function getBuildingsByLocation(req, res) {
const getBuildingsByLocation = asyncController(async (req: express.Request, res: express.Response) => {
const { lng, lat } = req.query;
buildingService.queryBuildingsAtPoint(lng, lat).then(function (result) {
try {
const result = await buildingService.queryBuildingsAtPoint(lng, lat);
res.send(result);
}).catch(function (error) {
} catch (error) {
console.error(error);
res.send({ error: 'Database error' })
})
}
res.send({ error: 'Database error' });
}
});
// GET buildings by reference (UPRN/TOID or other identifier)
function getBuildingsByReference(req, res) {
const getBuildingsByReference = asyncController(async (req: express.Request, res: express.Response) => {
const { key, id } = req.query;
buildingService.queryBuildingsByReference(key, id).then(function (result) {
try {
const result = await buildingService.queryBuildingsByReference(key, id);
res.send(result);
}).catch(function (error) {
} catch (error) {
console.error(error);
res.send({ error: 'Database error' })
})
}
res.send({ error: 'Database error' });
}
});
// GET individual building, POST building updates
function getBuildingById(req, res) {
const getBuildingById = asyncController(async (req: express.Request, res: express.Response) => {
const { building_id } = req.params;
buildingService.getBuildingById(building_id).then(function (result) {
try {
const result = await buildingService.getBuildingById(building_id);
res.send(result);
}).catch(function (error) {
} catch(error) {
console.error(error);
res.send({ error: 'Database error' })
})
}
res.send({ error: 'Database error' });
}
});
function updateBuildingById(req, res) {
const updateBuildingById = asyncController(async (req: express.Request, res: express.Response) => {
if (req.session.user_id) {
updateBuilding(req, res, req.session.user_id);
await updateBuilding(req, res, req.session.user_id);
} else if (req.query.api_key) {
userService.authAPIUser(req.query.api_key)
.then(function (user) {
updateBuilding(req, res, user.user_id)
})
.catch(function (err) {
console.error(err);
res.send({ error: 'Must be logged in' });
});
try {
const user = await userService.authAPIUser(req.query.api_key);
await updateBuilding(req, res, user.user_id);
} catch(err) {
console.error(err);
res.send({ error: 'Must be logged in' });
}
} else {
res.send({ error: 'Must be logged in' });
}
}
});
function updateBuilding(req, res, userId) {
async function updateBuilding(req: express.Request, res: express.Response, userId: string) {
const { building_id } = req.params;
const building = req.body;
buildingService.saveBuilding(building_id, building, userId).then(building => {
if (building.error) {
res.send(building)
return
}
const buildingUpdate = req.body;
try {
const building = await buildingService.saveBuilding(building_id, buildingUpdate, userId);
if (typeof (building) === 'undefined') {
res.send({ error: 'Database error' })
return
return res.send({ error: 'Database error' });
}
res.send(building)
}).catch(
() => res.send({ error: 'Database error' })
)
if (building.error) {
return res.send(building);
}
res.send(building);
} catch(err) {
res.send({ error: 'Database error' });
}
}
// GET building UPRNs
function getBuildingUPRNsById(req, res) {
const getBuildingUPRNsById = asyncController(async (req: express.Request, res: express.Response) => {
const { building_id } = req.params;
buildingService.getBuildingUPRNsById(building_id).then(function (result) {
try {
const result = await buildingService.getBuildingUPRNsById(building_id);
if (typeof (result) === 'undefined') {
res.send({ error: 'Database error' })
return
return res.send({ error: 'Database error' });
}
res.send({
uprns: result
});
}).catch(function (error) {
res.send({uprns: result});
} catch(error) {
console.error(error);
res.send({ error: 'Database error' })
})
}
res.send({ error: 'Database error' });
}
});
// GET/POST like building
function getBuildingLikeById(req, res) {
const getBuildingLikeById = asyncController(async (req: express.Request, res: express.Response) => {
if (!req.session.user_id) {
res.send({ like: false }); // not logged in, so cannot have liked
return
return res.send({ like: false }); // not logged in, so cannot have liked
}
const { building_id } = req.params;
buildingService.getBuildingLikeById(building_id, req.session.user_id).then(like => {
// any value returned means like
res.send({ like: like })
}).catch(
() => res.send({ error: 'Database error' })
)
}
try {
const like = await buildingService.getBuildingLikeById(building_id, req.session.user_id);
function updateBuildingLikeById(req, res) {
if (!req.session.user_id) {
res.send({ error: 'Must be logged in' });
return
// any value returned means like
res.send({ like: like });
} catch(error) {
res.send({ error: 'Database error' })
}
});
const updateBuildingLikeById = asyncController(async (req: express.Request, res: express.Response) => {
if (!req.session.user_id) {
return res.send({ error: 'Must be logged in' });
}
const { building_id } = req.params;
const { like } = req.body;
if (like) {
buildingService.likeBuilding(building_id, req.session.user_id).then(building => {
if (building.error) {
res.send(building)
return
}
if (typeof (building) === 'undefined') {
res.send({ error: 'Database error' })
return
}
res.send(building)
}).catch(
() => res.send({ error: 'Database error' })
)
} else {
buildingService.unlikeBuilding(building_id, req.session.user_id).then(building => {
if (building.error) {
res.send(building)
return
}
if (typeof (building) === 'undefined') {
res.send({ error: 'Database error' })
return
}
res.send(building)
}).catch(
() => res.send({ error: 'Database error' })
)
try {
const building = like ?
await buildingService.likeBuilding(building_id, req.session.user_id) :
await buildingService.unlikeBuilding(building_id, req.session.user_id);
if (building.error) {
return res.send(building);
}
if (typeof (building) === 'undefined') {
return res.send({ error: 'Database error' });
}
res.send(building);
} catch(error) {
res.send({ error: 'Database error' });
}
}
});
export default {
getBuildingsByLocation,

View File

@ -8,23 +8,22 @@ import { TokenVerificationError } from '../services/passwordReset';
import asyncController from '../routes/asyncController';
import { ValidationError } from '../validation';
function createUser(req, res) {
const createUser = asyncController(async (req: express.Request, res: express.Response) => {
const user = req.body;
if (req.session.user_id) {
res.send({ error: 'Already signed in' });
return;
return res.send({ error: 'Already signed in' });
}
if (user.email) {
if (user.email != user.confirm_email) {
res.send({ error: 'Email did not match confirmation.' });
return;
return res.send({ error: 'Email did not match confirmation.' });
}
} else {
user.email = null;
}
userService.createUser(user).then(function (result) {
try {
const result = await userService.createUser(user);
if (result.user_id) {
req.session.user_id = result.user_id;
res.send({ user_id: result.user_id });
@ -32,39 +31,40 @@ function createUser(req, res) {
req.session.user_id = undefined;
res.send({ error: result.error });
}
}).catch(function (err) {
} catch(err) {
console.error(err);
res.send(err);
});
}
}
});
function getCurrentUser(req, res) {
const getCurrentUser = asyncController(async (req: express.Request, res: express.Response) => {
if (!req.session.user_id) {
res.send({ error: 'Must be logged in' });
return;
return res.send({ error: 'Must be logged in' });
}
userService.getUserById(req.session.user_id).then(function (user) {
try {
const user = await userService.getUserById(req.session.user_id);
res.send(user);
}).catch(function (error) {
} catch(error) {
res.send(error);
});
}
}
});
function deleteCurrentUser(req, res) {
const deleteCurrentUser = asyncController(async (req: express.Request, res: express.Response) => {
if (!req.session.user_id) {
return res.send({ error: 'Must be logged in' });
}
console.log(`Deleting user ${req.session.user_id}`);
userService.deleteUser(req.session.user_id).then(
() => userService.logout(req.session)
).then(() => {
try {
await userService.deleteUser(req.session.user_id);
await userService.logout(req.session);
res.send({ success: true });
}).catch(err => {
} catch(err) {
res.send({ error: err });
});
}
}
});
const resetPassword = asyncController(async function(req: express.Request, res: express.Response) {
if(req.body == undefined || (req.body.email == undefined && req.body.token == undefined)) {

View File

@ -5,6 +5,7 @@
import db from '../../db';
import { tileCache } from '../../tiles/rendererDefinition';
import { BoundingBox } from '../../tiles/types';
import { ITask } from 'pg-promise';
// data type note: PostgreSQL bigint (64-bit) is handled as string in JavaScript, because of
// JavaScript numerics are 64-bit double, giving only partial coverage.
@ -18,282 +19,289 @@ const serializable = new TransactionMode({
readOnly: false
});
function queryBuildingsAtPoint(lng, lat) {
return db.manyOrNone(
`SELECT b.*
FROM buildings as b, geometries as g
WHERE
b.geometry_id = g.geometry_id
AND
ST_Intersects(
ST_Transform(
ST_SetSRID(ST_Point($1, $2), 4326),
3857
),
geometry_geom
)
`,
[lng, lat]
).catch(function (error) {
console.error(error);
return undefined;
});
}
function queryBuildingsByReference(key, id) {
if (key === 'toid') {
return db.manyOrNone(
`SELECT
*
FROM
buildings
async function queryBuildingsAtPoint(lng: number, lat: number) {
try {
return await db.manyOrNone(
`SELECT b.*
FROM buildings as b, geometries as g
WHERE
ref_toid = $1
`,
[id]
).catch(function (error) {
console.error(error);
return undefined;
});
}
if (key === 'uprn') {
return db.manyOrNone(
`SELECT
b.*
FROM
buildings as b, building_properties as p
WHERE
b.building_id = p.building_id
b.geometry_id = g.geometry_id
AND
p.uprn = $1
ST_Intersects(
ST_Transform(
ST_SetSRID(ST_Point($1, $2), 4326),
3857
),
geometry_geom
)
`,
[id]
).catch(function (error) {
console.error(error);
return undefined;
});
[lng, lat]
);
} catch(error) {
console.error(error);
return undefined;
}
return Promise.resolve({ error: 'Key must be UPRN or TOID' });
}
function getBuildingById(id) {
return db.one(
'SELECT * FROM buildings WHERE building_id = $1',
[id]
).then((building) => {
return getBuildingEditHistory(id).then((edit_history) => {
building.edit_history = edit_history
return building
})
}).catch(function (error) {
console.error(error);
return undefined;
});
}
function getBuildingEditHistory(id) {
return db.manyOrNone(
`SELECT log_id as revision_id, forward_patch, reverse_patch, date_trunc('minute', log_timestamp), username
FROM logs, users
WHERE building_id = $1 AND logs.user_id = users.user_id`,
[id]
).then((data) => {
return data
}).catch(function (error) {
console.error(error);
return []
});
}
function getBuildingLikeById(buildingId, userId) {
return db.oneOrNone(
'SELECT true as like FROM building_user_likes WHERE building_id = $1 and user_id = $2 LIMIT 1',
[buildingId, userId]
).then(res => {
return res && res.like
}).catch(function (error) {
console.error(error);
return undefined;
});
}
function getBuildingUPRNsById(id) {
return db.any(
'SELECT uprn, parent_uprn FROM building_properties WHERE building_id = $1',
[id]
).catch(function (error) {
console.error(error);
return undefined;
});
}
function saveBuilding(buildingId, building, userId) {
// remove read-only fields from consideration
delete building.building_id;
delete building.revision_id;
delete building.geometry_id;
// start transaction around save operation
// - select and compare to identify changeset
// - insert changeset
// - update to latest state
// commit or rollback (repeated-read sufficient? or serializable?)
return db.tx(t => {
return t.one(
'SELECT * FROM buildings WHERE building_id = $1 FOR UPDATE;',
[buildingId]
).then(oldBuilding => {
const patches = compare(oldBuilding, building, BUILDING_FIELD_WHITELIST);
console.log('Patching', buildingId, patches)
const forward = patches[0];
const reverse = patches[1];
if (Object.keys(forward).length === 0) {
return Promise.reject('No change provided')
}
return t.one(
`INSERT INTO logs (
forward_patch, reverse_patch, building_id, user_id
) VALUES (
$1:json, $2:json, $3, $4
) RETURNING log_id
async function queryBuildingsByReference(key: string, ref: string) {
try {
if (key === 'toid') {
return await db.manyOrNone(
`SELECT
*
FROM
buildings
WHERE
ref_toid = $1
`,
[forward, reverse, buildingId, userId]
).then(revision => {
const sets = db.$config.pgp.helpers.sets(forward);
console.log('Setting', buildingId, sets)
return t.one(
`UPDATE
buildings
SET
revision_id = $1,
$2:raw
WHERE
building_id = $3
RETURNING
*
`,
[revision.log_id, sets, buildingId]
).then((data) => {
expireBuildingTileCache(buildingId)
return data
})
});
[ref]
);
} else if (key === 'uprn') {
return await db.manyOrNone(
`SELECT
b.*
FROM
buildings as b, building_properties as p
WHERE
b.building_id = p.building_id
AND
p.uprn = $1
`,
[ref]
);
} else {
return { error: 'Key must be UPRN or TOID' };
}
} catch(err) {
console.error(err);
return undefined;
}
}
async function getBuildingById(id: number) {
try {
const building = await db.one(
'SELECT * FROM buildings WHERE building_id = $1',
[id]
);
building.edit_history = await getBuildingEditHistory(id);
return building;
} catch(error) {
console.error(error);
return undefined;
}
}
async function getBuildingEditHistory(id: number) {
try {
return await db.manyOrNone(
`SELECT log_id as revision_id, forward_patch, reverse_patch, date_trunc('minute', log_timestamp), username
FROM logs, users
WHERE building_id = $1 AND logs.user_id = users.user_id`,
[id]
);
} catch(error) {
console.error(error);
return [];
}
}
async function getBuildingLikeById(buildingId: number, userId: string) {
try {
const res = await db.oneOrNone(
'SELECT true as like FROM building_user_likes WHERE building_id = $1 and user_id = $2 LIMIT 1',
[buildingId, userId]
);
return res && res.like;
} catch(error) {
console.error(error);
return undefined;
}
}
async function getBuildingUPRNsById(id: number) {
try {
return await db.any(
'SELECT uprn, parent_uprn FROM building_properties WHERE building_id = $1',
[id]
);
} catch(error) {
console.error(error);
return undefined;
}
}
async function saveBuilding(buildingId: number, building: any, userId: string) { // TODO add proper building type
try {
return await updateBuildingData(buildingId, userId, async () => {
// remove read-only fields from consideration
delete building.building_id;
delete building.revision_id;
delete building.geometry_id;
// return whitelisted fields to update
return pickAttributesToUpdate(building, BUILDING_FIELD_WHITELIST);
});
}).catch(function (error) {
} catch(error) {
console.error(error);
return { error: error };
});
}
}
function likeBuilding(buildingId, userId) {
// start transaction around save operation
// - insert building-user like
// - count total likes
// - insert changeset
// - update building to latest state
// commit or rollback (serializable - could be more compact?)
return db.tx({mode: serializable}, t => {
return t.none(
'INSERT INTO building_user_likes ( building_id, user_id ) VALUES ($1, $2);',
[buildingId, userId]
).then(() => {
return t.one(
'SELECT count(*) as likes FROM building_user_likes WHERE building_id = $1;',
[buildingId]
).then(building => {
return t.one(
`INSERT INTO logs (
forward_patch, building_id, user_id
) VALUES (
$1:json, $2, $3
) RETURNING log_id
`,
[{ likes_total: building.likes }, buildingId, userId]
).then(revision => {
return t.one(
`UPDATE buildings
SET
revision_id = $1,
likes_total = $2
WHERE
building_id = $3
RETURNING
*
`,
[revision.log_id, building.likes, buildingId]
).then((data) => {
expireBuildingTileCache(buildingId)
return data
})
})
});
});
}).catch(function (error) {
async function likeBuilding(buildingId: number, userId: string) {
try {
return await updateBuildingData(
buildingId,
userId,
async (t) => {
// return total like count after update
return getBuildingLikeCount(buildingId, t);
},
async (t) => {
// insert building-user like
await t.none(
'INSERT INTO building_user_likes ( building_id, user_id ) VALUES ($1, $2);',
[buildingId, userId]
);
},
);
} catch (error) {
console.error(error);
if (error.detail && error.detail.includes('already exists')) {
// 'already exists' is thrown if user already liked it
return { error: 'It looks like you already like that building!' };
} else {
return undefined
return undefined;
}
});
}
}
function unlikeBuilding(buildingId, userId) {
// start transaction around save operation
// - insert building-user like
// - count total likes
// - insert changeset
// - update building to latest state
// commit or rollback (serializable - could be more compact?)
return db.tx({mode: serializable}, t => {
return t.none(
'DELETE FROM building_user_likes WHERE building_id = $1 AND user_id = $2;',
[buildingId, userId]
).then(() => {
return t.one(
'SELECT count(*) as likes FROM building_user_likes WHERE building_id = $1;',
[buildingId]
).then(building => {
return t.one(
`INSERT INTO logs (
forward_patch, building_id, user_id
async function unlikeBuilding(buildingId: number, userId: string) {
try {
return await updateBuildingData(
buildingId,
userId,
async (t) => {
// return total like count after update
return getBuildingLikeCount(buildingId, t);
},
async (t) => {
// remove building-user like
const result = await t.result(
'DELETE FROM building_user_likes WHERE building_id = $1 AND user_id = $2;',
[buildingId, userId]
);
if (result.rowCount === 0) {
throw new Error('No change');
}
},
);
} catch(error) {
console.error(error);
if (error.message === 'No change') {
// 'No change' is thrown if user doesn't like this building
return { error: 'It looks like you have already revoked your like for that building!' };
} else {
return undefined;
}
}
}
// === Utility functions ===
function pickAttributesToUpdate(obj: any, fieldWhitelist: Set<string>) {
const subObject = {};
for (let [key, value] of Object.entries(obj)) {
if(fieldWhitelist.has(key)) {
subObject[key] = value;
}
}
return subObject;
}
/**
*
* @param buildingId ID of the building to count likes for
* @param t The database context inside which the count should happen
*/
function getBuildingLikeCount(buildingId: number, t: ITask<unknown>) {
return t.one(
'SELECT count(*) as likes_total FROM building_user_likes WHERE building_id = $1;',
[buildingId]
);
}
/**
* Carry out an update of the buildings data. Allows for running any custom database operations before the main update.
* All db hooks get passed a transaction.
* @param buildingId The ID of the building to update
* @param userId The ID of the user updating the data
* @param getUpdateValue Function returning the set of attribute to update for the building
* @param preUpdateDbAction Any db operations to carry out before updating the buildings table (mostly intended for updating the user likes table)
*/
async function updateBuildingData(
buildingId: number,
userId: string,
getUpdateValue: (t: ITask<any>) => Promise<object>,
preUpdateDbAction?: (t: ITask<any>) => Promise<void>,
) {
return await db.tx({mode: serializable}, async t => {
if (preUpdateDbAction != undefined) {
await preUpdateDbAction(t);
}
const update = await getUpdateValue(t);
const oldBuilding = await t.one(
'SELECT * FROM buildings WHERE building_id = $1 FOR UPDATE;',
[buildingId]
);
console.log(update);
const patches = compare(oldBuilding, update);
console.log('Patching', buildingId, patches)
const [forward, reverse] = patches;
if (Object.keys(forward).length === 0) {
throw 'No change provided';
}
const revision = await t.one(
`INSERT INTO logs (
forward_patch, reverse_patch, building_id, user_id
) VALUES (
$1:json, $2, $3
$1:json, $2:json, $3, $4
) RETURNING log_id
`,
[{ likes_total: building.likes }, buildingId, userId]
).then(revision => {
return t.one(
`UPDATE buildings
SET
revision_id = $1,
likes_total = $2
WHERE
building_id = $3
RETURNING
*
`,
[revision.log_id, building.likes, buildingId]
).then((data) => {
expireBuildingTileCache(buildingId)
return data
})
})
});
});
}).catch(function (error) {
console.error(error);
if (error.detail && error.detail.includes('already exists')) {
// 'already exists' is thrown if user already liked it
return { error: 'It looks like you already like that building!' };
} else {
return undefined
}
[forward, reverse, buildingId, userId]
);
const sets = db.$config.pgp.helpers.sets(forward);
console.log('Setting', buildingId, sets);
const data = await t.one(
`UPDATE
buildings
SET
revision_id = $1,
$2:raw
WHERE
building_id = $3
RETURNING
*
`,
[revision.log_id, sets, buildingId]
);
expireBuildingTileCache(buildingId);
return data;
});
}
function privateQueryBuildingBBOX(buildingId){
function privateQueryBuildingBBOX(buildingId: number){
return db.one(
`SELECT
ST_XMin(envelope) as xmin,
@ -310,14 +318,13 @@ function privateQueryBuildingBBOX(buildingId){
b.building_id = $1
) as envelope`,
[buildingId]
)
);
}
function expireBuildingTileCache(buildingId) {
privateQueryBuildingBBOX(buildingId).then((bbox) => {
const buildingBbox: BoundingBox = [bbox.xmax, bbox.ymax, bbox.xmin, bbox.ymin];
tileCache.removeAllAtBbox(buildingBbox);
})
async function expireBuildingTileCache(buildingId: number) {
const bbox = await privateQueryBuildingBBOX(buildingId)
const buildingBbox: BoundingBox = [bbox.xmax, bbox.ymax, bbox.xmin, bbox.ymin];
tileCache.removeAllAtBbox(buildingBbox);
}
const BUILDING_FIELD_WHITELIST = new Set([
@ -385,16 +392,16 @@ const BUILDING_FIELD_WHITELIST = new Set([
* @param {Set} whitelist
* @returns {[object, object]}
*/
function compare(oldObj, newObj, whitelist) {
const reverse = {}
const forward = {}
function compare(oldObj: object, newObj: object): [object, object] {
const reverse = {};
const forward = {};
for (const [key, value] of Object.entries(newObj)) {
if (oldObj[key] !== value && whitelist.has(key)) {
if (oldObj[key] != value) {
reverse[key] = oldObj[key];
forward[key] = value;
}
}
return [forward, reverse]
return [forward, reverse];
}
export {

View File

@ -6,173 +6,185 @@ import { errors } from 'pg-promise';
import db from '../../db';
import { validateUsername, ValidationError, validatePassword } from '../validation';
import { promisify } from 'util';
function createUser(user) {
async function createUser(user) {
try {
validateUsername(user.username);
validatePassword(user.password);
} catch(err) {
if (err instanceof ValidationError) {
return Promise.reject({ error: err.message });
throw { error: err.message };
} else throw err;
}
return db.one(
`INSERT
INTO users (
user_id,
username,
email,
pass
) VALUES (
gen_random_uuid(),
$1,
$2,
crypt($3, gen_salt('bf'))
) RETURNING user_id
`, [
user.username,
user.email,
user.password
]
).catch(function (error) {
console.error('Error:', error)
try {
return await db.one(
`INSERT
INTO users (
user_id,
username,
email,
pass
) VALUES (
gen_random_uuid(),
$1,
$2,
crypt($3, gen_salt('bf'))
) RETURNING user_id
`, [
user.username,
user.email,
user.password
]
);
} catch(error) {
console.error('Error:', error);
if (error.detail.indexOf('already exists') !== -1) {
if (error.detail.indexOf('username') !== -1) {
if (error.detail.includes('already exists')) {
if (error.detail.includes('username')) {
return { error: 'Username already registered' };
} else if (error.detail.indexOf('email') !== -1) {
} else if (error.detail.includes('email')) {
return { error: 'Email already registered' };
}
}
return { error: 'Database error' }
});
return { error: 'Database error' };
}
}
function authUser(username, password) {
return db.one(
`SELECT
user_id,
(
pass = crypt($2, pass)
) AS auth_ok
FROM users
WHERE
username = $1
`, [
username,
password
]
).then(function (user) {
async function authUser(username: string, password: string) {
try {
const user = await db.one(
`SELECT
user_id,
(
pass = crypt($2, pass)
) AS auth_ok
FROM users
WHERE
username = $1
`, [
username,
password
]
);
if (user && user.auth_ok) {
return { user_id: user.user_id }
} else {
return { error: 'Username or password not recognised' }
}
}).catch(function (err) {
} catch(err) {
if (err instanceof errors.QueryResultError) {
console.error(`Authentication failed for user ${username}`);
return { error: 'Username or password not recognised' };
}
console.error('Error:', err);
return { error: 'Database error' };
})
}
}
function getUserById(id) {
return db.one(
`SELECT
username, email, registered, api_key
FROM
users
WHERE
user_id = $1
`, [
id
]
).catch(function (error) {
async function getUserById(id: string) {
try {
return await db.one(
`SELECT
username, email, registered, api_key
FROM
users
WHERE
user_id = $1
`, [
id
]
);
} catch(error) {
console.error('Error:', error)
return undefined;
});
}
}
function getUserByEmail(email: string) {
return db.one(
`SELECT
user_id, username, email
FROM
users
WHERE
email = $1
`, [email]
).catch(function(error) {
async function getUserByEmail(email: string) {
try {
return db.one(
`SELECT
user_id, username, email
FROM
users
WHERE
email = $1
`, [email]
);
} catch(error) {
console.error('Error:', error);
return undefined;
});
}
}
function getNewUserAPIKey(id) {
return db.one(
`UPDATE
users
SET
api_key = gen_random_uuid()
WHERE
user_id = $1
RETURNING
api_key
`, [
id
]
).catch(function (error) {
async function getNewUserAPIKey(id: string) {
try{
return db.one(
`UPDATE
users
SET
api_key = gen_random_uuid()
WHERE
user_id = $1
RETURNING
api_key
`, [
id
]
);
} catch(error) {
console.error('Error:', error)
return { error: 'Failed to generate new API key.' };
});
}
}
function authAPIUser(key) {
return db.one(
`SELECT
user_id
FROM
users
WHERE
api_key = $1
`, [
key
]
).catch(function (error) {
async function authAPIUser(key: string) {
try {
return await db.one(
`SELECT
user_id
FROM
users
WHERE
api_key = $1
`, [
key
]
);
} catch(error) {
console.error('Error:', error)
return undefined;
});
}
}
function deleteUser(id) {
return db.none(
`UPDATE users
SET
email = null,
pass = null,
api_key = null,
username = concat('deleted_', cast(user_id as char(13))),
is_deleted = true,
deleted_on = now() at time zone 'utc'
WHERE user_id = $1
`, [id]
).catch((error) => {
async function deleteUser(id: string) {
try {
return await db.none(
`UPDATE users
SET
email = null,
pass = null,
api_key = null,
username = concat('deleted_', cast(user_id as char(13))),
is_deleted = true,
deleted_on = now() at time zone 'utc'
WHERE user_id = $1
`, [id]
);
} catch(error) {
console.error('Error:', error);
return {error: 'Database error'};
});
}
}
function logout(session: Express.Session) {
return new Promise((resolve, reject) => {
session.user_id = undefined;
session.destroy(err => {
if (err) return reject(err);
return resolve();
});
});
function logout(session: Express.Session): Promise<void> {
session.user_id = undefined;
return promisify(session.destroy.bind(session))();
}
export {