137 lines
2.9 KiB
JavaScript
137 lines
2.9 KiB
JavaScript
import { createCache } from '$lib/cache.server';
|
|
import { cacheUpdater, cachedMethod, refExtendCachedMethod } from './root';
|
|
import { sql } from '$lib/db.server';
|
|
import { argon2id, hash, verify } from 'argon2';
|
|
import { PostgresError } from 'postgres';
|
|
|
|
const cache = createCache();
|
|
|
|
/**
|
|
* @template T
|
|
* @typedef {import('$types/base').Result<T>} Result<T>
|
|
*/
|
|
|
|
/**
|
|
* @typedef {import('$types/base').User} User
|
|
*/
|
|
|
|
/**
|
|
* @param {import('postgres').Row} row
|
|
* @returns {User}
|
|
*/
|
|
function parseUserFromRow(row) {
|
|
return {
|
|
id: row['id'],
|
|
name: row['username'],
|
|
joinDate: row['join_time']
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {Result<User>} users
|
|
* @returns {Result<User>}
|
|
*/
|
|
const updateUserCache = cacheUpdater(cache);
|
|
|
|
/**
|
|
* @param {number[]} user_ids
|
|
* @returns {Promise<Result<User>>}
|
|
*/
|
|
export const getUsersCached = cachedMethod(cache, getUsers);
|
|
|
|
export const getUsersCachedByRef = refExtendCachedMethod(getUsersCached);
|
|
|
|
/**
|
|
* @param {number[]} user_ids
|
|
* @returns {Promise<Result<User>>}
|
|
*/
|
|
export async function getUsers(user_ids) {
|
|
if (user_ids.length == 0) return new Map();
|
|
|
|
const query = sql`
|
|
SELECT id, username, join_time
|
|
FROM doki8902.user
|
|
WHERE id IN ${ sql(user_ids) };`;
|
|
|
|
let users = await query;
|
|
|
|
/**
|
|
* @type {Result<User>}
|
|
*/
|
|
let result = new Map();
|
|
|
|
users.forEach(row => {
|
|
result.set(row['id'], parseUserFromRow(row));
|
|
})
|
|
|
|
return updateUserCache(result);
|
|
}
|
|
|
|
/**
|
|
* @param {number} user_id
|
|
* @returns {Promise<User | import('$types/status').Error>}
|
|
*/
|
|
export async function getUser(user_id) {
|
|
const users = await getUsers([user_id]);
|
|
|
|
return users.get(user_id) || {
|
|
error: true,
|
|
msg: `Could not find user of ID ${user_id}`
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {string} username
|
|
* @param {string} password
|
|
* @returns {Promise<import('$types/status').Success | import('$types/status').Error>}
|
|
*/
|
|
export async function registerUser(username, password) {
|
|
const hashedPassword = await hash(password, {
|
|
type: argon2id,
|
|
memoryCost: 2 ** 16,
|
|
timeCost: 4,
|
|
parallelism: 1,
|
|
hashLength: 64,
|
|
});
|
|
|
|
const insert = sql`
|
|
INSERT INTO doki8902.user (username, password)
|
|
VALUES (${ username }, ${ hashedPassword });`;
|
|
|
|
try {
|
|
await insert;
|
|
|
|
} catch (e) {
|
|
if (e && typeof(e) === 'object' && 'name' in e && e.name === 'PostgresError') {
|
|
const pgerr = /** @type {PostgresError} */ (e);
|
|
|
|
switch (pgerr.constraint_name) {
|
|
case 'idx_user_username':
|
|
return {
|
|
error: true,
|
|
msg: "Username taken",
|
|
};
|
|
|
|
case 'username_length_min':
|
|
return {
|
|
error: true,
|
|
msg: "Username has invalid length",
|
|
};
|
|
|
|
case 'username_valid_symbols':
|
|
return {
|
|
error: true,
|
|
msg: "Username contains invalid symbols",
|
|
};
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
};
|
|
}
|