colouring-montreal/app/src/api/services/user.ts
mz8i c63f42f921
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
2019-10-21 15:19:35 +01:00

200 lines
4.6 KiB
TypeScript

/**
* User data access
*
*/
import { errors } from 'pg-promise';
import db from '../../db';
import { validateUsername, ValidationError, validatePassword } from '../validation';
import { promisify } from 'util';
async function createUser(user) {
try {
validateUsername(user.username);
validatePassword(user.password);
} catch(err) {
if (err instanceof ValidationError) {
throw { error: err.message };
} else throw err;
}
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.includes('already exists')) {
if (error.detail.includes('username')) {
return { error: 'Username already registered' };
} else if (error.detail.includes('email')) {
return { error: 'Email already registered' };
}
}
return { error: 'Database error' };
}
}
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(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' };
}
}
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;
}
}
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;
}
}
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.' };
}
}
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;
}
}
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): Promise<void> {
session.user_id = undefined;
return promisify(session.destroy.bind(session))();
}
export {
getUserById,
getUserByEmail,
createUser,
authUser,
getNewUserAPIKey,
authAPIUser,
deleteUser,
logout
};