244 lines
5.9 KiB
JavaScript
244 lines
5.9 KiB
JavaScript
import { sql } from '$lib/db.server';
|
|
import { getCategoriesCachedByRef, getCategoryCached } from './category';
|
|
import { isPostgresError } from './root';
|
|
import { getUser, getUsersCachedByRef, sqlUserFromToken } from './user';
|
|
|
|
/**
|
|
* @typedef {import('$types/base').Post} Post
|
|
* @typedef {import('$types/base').PostMetrics} PostMetrics
|
|
*/
|
|
|
|
/**
|
|
* @param {import('postgres').Row} row
|
|
* @returns {PostMetrics}
|
|
*/
|
|
function parsePostMetricsFromRow(row) {
|
|
return {
|
|
commentCount: BigInt(row['comment_count']),
|
|
userCount: BigInt(row['user_count']),
|
|
latestActivity: row['latest_activity'],
|
|
engagement: BigInt(row['engagement']),
|
|
age: row['age'],
|
|
relevancy: row['relevancy'],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {import('$types/base').User | null} author
|
|
* @param {import('$types/base').Category} category
|
|
* @param {import('postgres').Row} row
|
|
* @param {boolean} withMetrics
|
|
* @returns {Post}
|
|
*/
|
|
function parsePostFromRow(author, category, row, withMetrics = false) {
|
|
return {
|
|
id: row['id'],
|
|
author: author,
|
|
name: row['name'],
|
|
category: category,
|
|
content: row['latest_content'],
|
|
edited: row['edit_count'] > 1,
|
|
editCount: row['edit_count'] - 1,
|
|
postDate: row['created_date'],
|
|
rating: {
|
|
likes: BigInt(row['likes']),
|
|
dislikes: BigInt(row['dislikes']),
|
|
},
|
|
metrics: withMetrics ? parsePostMetricsFromRow(row) : null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {{
|
|
* category?: import('$types/base').Category | undefined
|
|
* }} opts
|
|
* @returns {Promise<number>}
|
|
*/
|
|
export async function getPostCount(opts = {}) {
|
|
const {
|
|
category = undefined
|
|
} = opts;
|
|
|
|
const filter = category ? sql`WHERE category_id = ${ category.id }` : sql``;
|
|
|
|
const query = sql`
|
|
SELECT COUNT(*)
|
|
FROM doki8902.message_post
|
|
${ filter };`;
|
|
|
|
const count = await query;
|
|
|
|
return count[0]['count'];
|
|
}
|
|
|
|
/**
|
|
* @param {{
|
|
* category?: import('$types/base').Category | undefined,
|
|
* limit?: number,
|
|
* offset?: number,
|
|
* withMetrics?: boolean
|
|
* }} opts
|
|
* @returns {Promise<Post[]>}
|
|
*/
|
|
export async function getPosts(opts = {}) {
|
|
const {
|
|
category = undefined,
|
|
limit = 10,
|
|
offset = 0,
|
|
withMetrics = false,
|
|
} = opts;
|
|
|
|
const filter = category ? sql`WHERE category_id = ${ category.id }` : sql``;
|
|
const metrics = withMetrics ? sql`, comment_count, user_count, latest_activity, engagement, age, relevancy` : sql``;
|
|
|
|
const query = sql`
|
|
SELECT id, author_id, name, category_id, latest_content, edit_count, created_date, likes, dislikes ${ metrics }
|
|
FROM doki8902.message_post
|
|
${ filter }
|
|
FETCH FIRST ${ limit } ROWS ONLY
|
|
OFFSET ${ offset };`;
|
|
|
|
const posts = await query;
|
|
|
|
const users = await getUsersCachedByRef(posts, p => p['author_id']);
|
|
const categories = await getCategoriesCachedByRef(posts, p => p['category_id']);
|
|
|
|
/**
|
|
* @type {Post[]}
|
|
*/
|
|
return posts.map(row => parsePostFromRow(
|
|
users.get(row['author_id']) || null,
|
|
/** @type {import('$types/base').Category} */ (categories.get(row['category_id'])),
|
|
row,
|
|
withMetrics
|
|
));
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {number} post_id
|
|
* @param {{
|
|
* withMetrics?: boolean
|
|
* }} opts
|
|
* @returns {Promise<Post | import('$types/status').Error>}
|
|
*/
|
|
export async function getPost(post_id, opts = {}) {
|
|
const {
|
|
withMetrics = false
|
|
} = opts;
|
|
|
|
const metrics = withMetrics ? sql`, comment_count, user_count, latest_activity, engagement, age, relevancy` : sql``;
|
|
|
|
const query = sql`
|
|
SELECT id, author_id, name, category_id, latest_content, edit_count, created_date, likes, dislikes ${ metrics }
|
|
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(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(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 parsePostFromRow(author, category, post, withMetrics);
|
|
}
|
|
|
|
/**
|
|
* @param {string} token
|
|
* @param {import('$types/base').Category} category
|
|
* @param {string} name
|
|
* @param {string} content
|
|
* @returns {Promise<import('$types/status').Success | import('$types/status').Error>}
|
|
*/
|
|
export async function createPost(token, category, name, content) {
|
|
const insert = sql`
|
|
INSERT INTO doki8902.message_post (author_id, category_id, name, latest_content)
|
|
VALUES (
|
|
(${ sqlUserFromToken(token) }),
|
|
${ category.id }, ${ name }, ${ content }
|
|
)
|
|
RETURNING id;`;
|
|
|
|
let result;
|
|
|
|
try {
|
|
result = await insert;
|
|
} catch (e) {
|
|
const pgerr = isPostgresError(e);
|
|
|
|
switch (pgerr?.constraint_name) {
|
|
case 'message_require_user':
|
|
return {
|
|
error: true,
|
|
msg: 'User token was invalid',
|
|
};
|
|
|
|
case 'post_category_id_fkey':
|
|
return {
|
|
error: true,
|
|
msg: 'Post category does not exist',
|
|
};
|
|
|
|
case 'content_min_length':
|
|
return {
|
|
error: true,
|
|
msg: 'Post content cannot be empty',
|
|
};
|
|
|
|
case 'name_min_length':
|
|
return {
|
|
error: true,
|
|
msg: 'Post name cannot be empty',
|
|
};
|
|
|
|
default:
|
|
console.log(e);
|
|
return {
|
|
error: true,
|
|
msg: 'Unknown error (notify dev)',
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
const postId = result[0]['id'];
|
|
|
|
return {
|
|
success: true,
|
|
result: postId,
|
|
};
|
|
}
|