Working data and basic demo pages
This commit is contained in:
@@ -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];
|
||||
}
|
||||
@@ -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']),
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user