Working data and basic demo pages

This commit is contained in:
2024-05-10 00:28:23 +03:00
parent e2d2599a95
commit 9983720bd1
21 changed files with 415 additions and 91 deletions
+75
View File
@@ -0,0 +1,75 @@
import { createCache } from '$lib/cache.server';
import { cacheUpdater, cachedMethod } from './root';
const cache = createCache();
/**
* @template T
* @typedef {import('$types/base').Result<T>} Result<T>
*/
/**
* @typedef {import('$types/base').Category} Category
*/
/**
* @param {Result<Category>} categories
* @returns {Result<Category>}
*/
const updateCategoryCache = cacheUpdater(cache);
/**
* @param {import('postgres').Sql} sql
* @param {number[]} user_ids
* @returns {Promise<Result<Category>>}
*/
export const getCategoriesCached = cachedMethod(cache, getCategories);
/**
* @param {import('postgres').Sql} sql
* @param {number[]} category_ids
* @returns {Promise<Result<Category>>}
*/
export async function getCategories(sql, category_ids) {
if (category_ids.length == 0) return {};
const query = sql`
SELECT id, name
FROM doki8902.post_category
WHERE id IN ${ sql(category_ids) };`;
let categories = await query;
/**
* @type {Result<Category>}
*/
let result = {};
categories.forEach(row => {
result[row['id']] = {
id: row['id'],
name: row['name']
}
})
return updateCategoryCache(result);
}
/**
*
* @param {import('postgres').Sql} sql
* @param {number} category_id
* @returns {Promise<Category | import('$types/error').Error>}
*/
export async function getCategoryCached(sql, category_id) {
const categories = await getCategoriesCached(sql, [category_id]);
if (Object.keys(categories).length == 0) {
return {
error: true,
msg: `Could not find Category of ID ${category_id}`
};
}
return categories[category_id];
}
+123
View File
@@ -0,0 +1,123 @@
import { getCategoriesCached, getCategoryCached } from './category';
import { getUser, getUsersCached } from './user';
/**
* @typedef {import('$types/base').Post} Post
*/
/**
* @param {import('postgres').Sql} sql
* @param {import('$types/base').Category | undefined} category
* @param {number} limit
* @param {number} offset
* @returns {Promise<Post[]>}
*/
export async function getPosts(sql, category = undefined, limit = 10, offset = 0) {
let filter;
if (category === undefined) {
filter = sql``;
} else {
filter = sql`WHERE category_id = ${ category.id }`;
}
const query = sql`
SELECT id, author_id, name, category_id, latest_content, created_date, likes, dislikes
FROM doki8902.message_post
${ filter }
FETCH FIRST ${ limit } ROWS ONLY
OFFSET ${ offset };`;
const posts = await query;
const users = await getUsersCached(sql, posts.map(row => {
return row['author_id'];
}));
const categories = await getCategoriesCached(sql, posts.map(row => {
return row['category_id'];
}));
/**
* @type {Post[]}
*/
return posts.map(row => {
return {
id: row['id'],
author: users[row['author_id']] || null,
name: row['name'],
category: categories[row['category_id']],
content: row['latest_content'],
post_date: row['created_date'],
rating: {
likes: BigInt(row['likes']),
dislikes: BigInt(row['dislikes']),
}
};
});
}
/**
*
* @param {import('postgres').Sql} sql
* @param {number} post_id
* @returns {Promise<Post | import('$types/error').Error>}
*/
export async function getPost(sql, post_id) {
const query = sql`
SELECT id, author_id, name, category_id, latest_content, created_date, likes, dislikes
FROM doki8902.message_post
WHERE id = ${ post_id };`;
const post = (await query).at(0);
if (!post) {
return {
error: true,
msg: `Could not find Post of ID ${ post_id }`
};
}
const user_guess = await getUser(sql, post['author_id']);
/**
* @type {import('$types/base').User | null}
*/
const author = function () {
if (Object.hasOwn(user_guess, 'error')) {
return null;
} else {
return /** @type {import('$types/base').User} */ (user_guess);
}
}();
const category_guess = await getCategoryCached(sql, post['category_id']);
if (Object.hasOwn(category_guess, 'error')) {
return {
error: true,
msg: `Post of ID ${ post_id } has an invalid Category ID ${ post['category_id'] }`
};
}
/**
* @type {import('$types/base').Category}
*/
const category = function () {
return /** @type {import('$types/base').Category} */ (category_guess);
}();
/**
* @type {Post}
*/
return {
id: post['id'],
author: author,
name: post['name'],
category: category,
content: post['latest_content'],
post_date: post['created_date'],
rating: {
likes: BigInt(post['likes']),
dislikes: BigInt(post['dislikes']),
}
};
}
+46
View File
@@ -0,0 +1,46 @@
/**
* @template T
* @param {import('node-cache')} cache
* @returns {function({[id: number]: T})}
*/
export const cacheUpdater = (cache) => {
return function updateUserCache(data) {
Object.keys(data).forEach(id => {
cache.set(parseInt(id), data[parseInt(id)]);
});
return data;
}
};
/**
* @template T
* @param {import('node-cache')} cache
* @param {function(import('postgres').Sql, number[]): Promise<{[id: number]: T}>} method
* @returns {function(import('postgres').Sql, number[]): Promise<{[id: number]: T}>}
*/
export const cachedMethod = (cache, method) => {
return async function(sql, ids) {
/**
* @type {{[id: number]: T}}
*/
let results = {};
/**
* @type {number[]}
*/
let missing = [];
ids.forEach(id => {
if (id === null || id === undefined)
return;
let user = cache.get(id);
if (user)
results[id] = user;
else
missing.push(id);
});
const remaining = await method(sql, missing);
return Object.assign({}, results, remaining);
};
}
+75
View File
@@ -0,0 +1,75 @@
import { createCache } from '$lib/cache.server';
import { cacheUpdater, cachedMethod } from './root';
const cache = createCache();
/**
* @template T
* @typedef {import('$types/base').Result<T>} Result<T>
*/
/**
* @typedef {import('$types/base').User} User
*/
/**
* @param {Result<User>} users
* @returns {Result<User>}
*/
const updateUserCache = cacheUpdater(cache);
/**
* @param {import('postgres').Sql} sql
* @param {number[]} user_ids
* @returns {Promise<Result<User>>}
*/
export const getUsersCached = cachedMethod(cache, getUsers);
/**
* @param {import('postgres').Sql} sql
* @param {number[]} user_ids
* @returns {Promise<Result<User>>}
*/
export async function getUsers(sql, user_ids) {
if (user_ids.length == 0) return {};
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 = {};
users.forEach(row => {
result[row['id']] = {
id: row['id'],
name: row['username'],
join_date: row['join_time']
}
})
return updateUserCache(result);
}
/**
* @param {import('postgres').Sql} sql
* @param {number} user_id
* @returns {Promise<User | import('$types/error').Error>}
*/
export async function getUser(sql, user_id) {
const users = await getUsers(sql, [user_id]);
if (Object.keys(users).length == 0) {
return {
error: true,
msg: `Could not find user of ID ${user_id}`
};
}
return users[user_id];
}