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,
};
}