Add basic knex methods to be used for /account/* pages
This commit is contained in:
parent
162f8fd9b5
commit
269aa6bfe6
152
integration_test/db/account.js
Normal file
152
integration_test/db/account.js
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
const AccountDB = require('../../lib/db/account').AccountDB;
|
||||||
|
const testDB = require('../testutil/db').testDB;
|
||||||
|
|
||||||
|
const accountDB = new AccountDB(testDB);
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
return testDB.knex.table('users').del();
|
||||||
|
}
|
||||||
|
|
||||||
|
function insert(user) {
|
||||||
|
return testDB.knex.table('users').insert(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch(params) {
|
||||||
|
return testDB.knex.table('users').where(params).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('AccountDB', () => {
|
||||||
|
let account, expected;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
account = {
|
||||||
|
name: 'test',
|
||||||
|
password: '',
|
||||||
|
global_rank: 1,
|
||||||
|
email: 'test@example.com',
|
||||||
|
profile: '{"image":"image.jpeg","text":"blah"}',
|
||||||
|
ip: '1.2.3.4',
|
||||||
|
time: 1500000000000,
|
||||||
|
name_dedupe: 'test'
|
||||||
|
};
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
name: 'test',
|
||||||
|
password: '',
|
||||||
|
global_rank: 1,
|
||||||
|
email: 'test@example.com',
|
||||||
|
profile: {
|
||||||
|
image: 'image.jpeg',
|
||||||
|
text: 'blah'
|
||||||
|
},
|
||||||
|
ip: '1.2.3.4',
|
||||||
|
time: new Date(1500000000000),
|
||||||
|
name_dedupe: 'test'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(cleanup);
|
||||||
|
|
||||||
|
describe('#getByName', () => {
|
||||||
|
it('retrieves an account by name', () => {
|
||||||
|
return insert(account).then(() => {
|
||||||
|
return accountDB.getByName('test');
|
||||||
|
}).then(retrieved => {
|
||||||
|
delete retrieved.id;
|
||||||
|
|
||||||
|
assert.deepStrictEqual(retrieved, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults a blank profile', () => {
|
||||||
|
account.profile = '';
|
||||||
|
expected.profile = { image: '', text: '' };
|
||||||
|
|
||||||
|
return insert(account).then(() => {
|
||||||
|
return accountDB.getByName('test');
|
||||||
|
}).then(retrieved => {
|
||||||
|
delete retrieved.id;
|
||||||
|
|
||||||
|
assert.deepStrictEqual(retrieved, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults an erroneous profile', () => {
|
||||||
|
account.profile = '{not real json';
|
||||||
|
expected.profile = { image: '', text: '' };
|
||||||
|
|
||||||
|
return insert(account).then(() => {
|
||||||
|
return accountDB.getByName('test');
|
||||||
|
}).then(retrieved => {
|
||||||
|
delete retrieved.id;
|
||||||
|
|
||||||
|
assert.deepStrictEqual(retrieved, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when no account is found', () => {
|
||||||
|
return accountDB.getByName('test').then(retrieved => {
|
||||||
|
assert.deepStrictEqual(retrieved, null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#updateByName', () => {
|
||||||
|
it('updates the password hash', () => {
|
||||||
|
return insert(account).then(() => {
|
||||||
|
return accountDB.updateByName(
|
||||||
|
account.name,
|
||||||
|
{ password: 'secret hash' }
|
||||||
|
);
|
||||||
|
}).then(() => {
|
||||||
|
return fetch({ name: account.name });
|
||||||
|
}).then(retrieved => {
|
||||||
|
assert.strictEqual(retrieved.password, 'secret hash');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the email', () => {
|
||||||
|
return insert(account).then(() => {
|
||||||
|
return accountDB.updateByName(
|
||||||
|
account.name,
|
||||||
|
{ email: 'bar@example.com' }
|
||||||
|
);
|
||||||
|
}).then(() => {
|
||||||
|
return fetch({ name: account.name });
|
||||||
|
}).then(retrieved => {
|
||||||
|
assert.strictEqual(retrieved.email, 'bar@example.com');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the profile', () => {
|
||||||
|
return insert(account).then(() => {
|
||||||
|
return accountDB.updateByName(
|
||||||
|
account.name,
|
||||||
|
{ profile: { image: 'shiggy.jpg', text: 'Costanza' } }
|
||||||
|
);
|
||||||
|
}).then(() => {
|
||||||
|
return fetch({ name: account.name });
|
||||||
|
}).then(retrieved => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
retrieved.profile,
|
||||||
|
'{"image":"shiggy.jpg","text":"Costanza"}'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('raises an error if the username does not exist', () => {
|
||||||
|
return accountDB.updateByName(
|
||||||
|
account.name,
|
||||||
|
{ password: 'secret hash' }
|
||||||
|
).then(() => {
|
||||||
|
throw new Error('Expected failure due to missing user');
|
||||||
|
}).catch(error => {
|
||||||
|
assert.strictEqual(
|
||||||
|
error.message,
|
||||||
|
'Cannot update: name "test" does not exist'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
65
src/db/account.js
Normal file
65
src/db/account.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
const LOGGER = require('@calzoneman/jsli')('AccountDB');
|
||||||
|
|
||||||
|
class AccountDB {
|
||||||
|
constructor(db) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
getByName(name) {
|
||||||
|
return this.db.runTransaction(async tx => {
|
||||||
|
const rows = await tx.table('users').where({ name }).select();
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapUser(rows[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateByName(name, changedFields) {
|
||||||
|
return this.db.runTransaction(tx => {
|
||||||
|
if (changedFields.profile) {
|
||||||
|
changedFields.profile = JSON.stringify(changedFields.profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.table('users')
|
||||||
|
.update(changedFields)
|
||||||
|
.where({ name })
|
||||||
|
.then(rowsUpdated => {
|
||||||
|
if (rowsUpdated === 0) {
|
||||||
|
throw new Error(`Cannot update: name "${name}" does not exist`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mapUser(user) {
|
||||||
|
// Backwards compatibility
|
||||||
|
// Maybe worth backfilling one day to be done with it?
|
||||||
|
try {
|
||||||
|
let profile;
|
||||||
|
|
||||||
|
if (!user.profile) {
|
||||||
|
profile = { image: '', text: '' };
|
||||||
|
} else {
|
||||||
|
profile = JSON.parse(user.profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!profile.image) profile.image = '';
|
||||||
|
if (!profile.text) profile.text = '';
|
||||||
|
|
||||||
|
user.profile = profile;
|
||||||
|
} catch (error) {
|
||||||
|
// TODO: backfill erroneous records and remove this check
|
||||||
|
LOGGER.warn('Invalid profile "%s": %s', user.profile, error);
|
||||||
|
user.profile = { image: '', text: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
user.time = new Date(user.time);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { AccountDB };
|
Loading…
Reference in a new issue