diff --git a/src/comp/comment.svelte b/src/comp/comment.svelte
new file mode 100644
index 0000000..8a58d3f
--- /dev/null
+++ b/src/comp/comment.svelte
@@ -0,0 +1,16 @@
+
+
+
+
{commentNode.parent.author?.name}
+
{commentNode.parent.content}
+
+ {#each commentNode.children as reply}
+
+ {/each}
+
+
diff --git a/src/lib/client/nodetree.js b/src/lib/client/nodetree.js
new file mode 100644
index 0000000..e94afc9
--- /dev/null
+++ b/src/lib/client/nodetree.js
@@ -0,0 +1,41 @@
+/**
+ * @typedef {import('$types/base').Comment} Comment
+ */
+
+/**
+ * @param {import('$types/base').Result} comments
+ * @returns {import('$types/base').CommentTreeNode[]}
+ */
+export function buildCommentTree(comments) {
+ /** @type {Comment[]} */
+ let roots = [];
+ /** @type {Map} */
+ let refs = new Map();
+
+ comments.forEach((comment, id) => {
+ if (comment.parentCommentId == null) {
+ roots.push(comment);
+ } else {
+ if (!refs.has(comment.parentCommentId)) {
+ refs.set(comment.parentCommentId, []);
+ }
+ refs.get(comment.parentCommentId)?.push(comment);
+ }
+ });
+
+ return roots.map(r => buildFromRoot(refs, r));
+}
+
+/**
+ * @param {Map} refs
+ * @param {Comment} comment
+ * @returns {import('$types/base').CommentTreeNode}
+ */
+function buildFromRoot(refs, comment) {
+ return {
+ parent: comment,
+ children: refs.get(comment.id)?.map(c => {
+ return buildFromRoot(refs, c);
+ }) || []
+ }
+}
diff --git a/src/lib/server/db/category.js b/src/lib/server/db/category.js
index cea443d..d2bf150 100644
--- a/src/lib/server/db/category.js
+++ b/src/lib/server/db/category.js
@@ -1,5 +1,5 @@
import { createCache } from '$lib/cache.server';
-import { cacheUpdater, cachedMethod } from './root';
+import { cacheUpdater, cachedMethod, refExtendCachedMethod } from './root';
const cache = createCache();
@@ -12,6 +12,17 @@ const cache = createCache();
* @typedef {import('$types/base').Category} Category
*/
+/**
+ * @param {import('postgres').Row} row
+ * @returns {Category}
+ */
+function parseCategoryFromRow(row) {
+ return {
+ id: row['id'],
+ name: row['name']
+ };
+}
+
/**
* @param {Result} categories
* @returns {Result}
@@ -25,13 +36,15 @@ const updateCategoryCache = cacheUpdater(cache);
*/
export const getCategoriesCached = cachedMethod(cache, getCategories);
+export const getCategoriesCachedByRef = refExtendCachedMethod(getCategoriesCached);
+
/**
* @param {import('postgres').Sql} sql
* @param {number[]} category_ids
* @returns {Promise>}
*/
export async function getCategories(sql, category_ids) {
- if (category_ids.length == 0) return {};
+ if (category_ids.length == 0) return new Map();
const query = sql`
SELECT id, name
@@ -43,13 +56,10 @@ export async function getCategories(sql, category_ids) {
/**
* @type {Result}
*/
- let result = {};
+ let result = new Map();
categories.forEach(row => {
- result[row['id']] = {
- id: row['id'],
- name: row['name']
- }
+ result.set(row['id'], parseCategoryFromRow(row));
})
return updateCategoryCache(result);
@@ -64,12 +74,8 @@ export async function getCategories(sql, category_ids) {
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];
+ return categories.get(category_id) || {
+ error: true,
+ msg: `Could not find Category of ID ${category_id}`
+ };
}
\ No newline at end of file
diff --git a/src/lib/server/db/comment.js b/src/lib/server/db/comment.js
new file mode 100644
index 0000000..84ddbf9
--- /dev/null
+++ b/src/lib/server/db/comment.js
@@ -0,0 +1,48 @@
+import { getUser, getUsersCached, getUsersCachedByRef } from './user';
+
+/**
+ * @typedef {import('$types/base').Comment} Comment
+ */
+
+/**
+ * @param {import('$types/base').User | null} author
+ * @param {import('postgres').Row} row
+ * @returns {Comment}
+ */
+function parseCommentFromRow(author, row) {
+ return {
+ id: row['id'],
+ author: author,
+ parentCommentId: row['parent_comment_id'],
+ content: row['latest_content'],
+ commentDate: row['created_date'],
+ rating: {
+ likes: BigInt(row['likes']),
+ dislikes: BigInt(row['dislikes']),
+ }
+ };
+}
+
+/**
+ * @param {import('postgres').Sql} sql
+ * @param {number} post_id
+ * @returns {Promise}
+ */
+export async function getCommentsForPost(sql, post_id) {
+ const query = sql`
+ SELECT id, author_id, latest_content, parent_comment_id, created_date, likes, dislikes
+ FROM doki8902.message_comment
+ WHERE post_id = ${ post_id };`;
+
+ const comments = await query;
+
+ const users = await getUsersCachedByRef(sql, comments, c => c['author_id']);
+
+ /**
+ * @type {Comment[]}
+ */
+ return comments.map(row => parseCommentFromRow(
+ users.get(row['author_id']) || null,
+ row
+ ));
+}
diff --git a/src/lib/server/db/post.js b/src/lib/server/db/post.js
index 339c16b..81ad5c2 100644
--- a/src/lib/server/db/post.js
+++ b/src/lib/server/db/post.js
@@ -1,10 +1,31 @@
-import { getCategoriesCached, getCategoryCached } from './category';
-import { getUser, getUsersCached } from './user';
+import { getCategoriesCached, getCategoriesCachedByRef, getCategoryCached } from './category';
+import { getUser, getUsersCached, getUsersCachedByRef } from './user';
/**
* @typedef {import('$types/base').Post} Post
*/
+/**
+ * @param {import('$types/base').User | null} author
+ * @param {import('$types/base').Category} category
+ * @param {import('postgres').Row} row
+ * @returns {Post}
+ */
+function parsePostFromRow(author, category, row) {
+ return {
+ id: row['id'],
+ author: author,
+ name: row['name'],
+ category: category,
+ content: row['latest_content'],
+ postDate: row['created_date'],
+ rating: {
+ likes: BigInt(row['likes']),
+ dislikes: BigInt(row['dislikes']),
+ }
+ };
+}
+
/**
* @param {import('postgres').Sql} sql
* @param {import('$types/base').Category | undefined} category
@@ -30,31 +51,17 @@ export async function getPosts(sql, category = undefined, limit = 10, offset = 0
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'];
- }));
+ const users = await getUsersCachedByRef(sql, posts, p => p['author_id']);
+ const categories = await getCategoriesCachedByRef(sql, posts, p => p['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']),
- }
- };
- });
+ return posts.map(row => parsePostFromRow(
+ users.get(row['author_id']) || null,
+ /** @type {import('$types/base').Category} */ (categories.get(row['category_id'])),
+ row
+ ));
}
/**
@@ -108,16 +115,5 @@ export async function getPost(sql, post_id) {
/**
* @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']),
- }
- };
+ return parsePostFromRow(author, category, post);
}
diff --git a/src/lib/server/db/root.js b/src/lib/server/db/root.js
index 41076b7..ce73d8a 100644
--- a/src/lib/server/db/root.js
+++ b/src/lib/server/db/root.js
@@ -1,12 +1,12 @@
/**
* @template T
* @param {import('node-cache')} cache
- * @returns {function({[id: number]: T})}
+ * @returns {function(import('$types/base').Result)}
*/
export const cacheUpdater = (cache) => {
return function updateUserCache(data) {
- Object.keys(data).forEach(id => {
- cache.set(parseInt(id), data[parseInt(id)]);
+ data.forEach((val, id) => {
+ cache.set(id, val);
});
return data;
}
@@ -15,15 +15,15 @@ export const cacheUpdater = (cache) => {
/**
* @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}>}
+ * @param {function(import('postgres').Sql, number[]): Promise>} method
+ * @returns {function(import('postgres').Sql, number[]): Promise>}
*/
export const cachedMethod = (cache, method) => {
return async function(sql, ids) {
/**
- * @type {{[id: number]: T}}
+ * @type {import('$types/base').Result}
*/
- let results = {};
+ let results = new Map();
/**
* @type {number[]}
*/
@@ -34,13 +34,24 @@ export const cachedMethod = (cache, method) => {
return;
let user = cache.get(id);
if (user)
- results[id] = user;
+ results.set(id, user);
else
missing.push(id);
});
const remaining = await method(sql, missing);
- return Object.assign({}, results, remaining);
+ return new Map([...results, ...remaining]);
};
}
+
+/**
+ * @template T
+ * @param {function(import('postgres').Sql, number[]): Promise>} cachedMethod
+ * @returns {function(import('postgres').Sql, import('postgres').RowList, function(import('postgres').Row): number): Promise>}
+ */
+export const refExtendCachedMethod = (cachedMethod) => {
+ return async function(sql, rows, getRef) {
+ return await cachedMethod(sql, rows.map(r => getRef(r)));
+ }
+}
diff --git a/src/lib/server/db/user.js b/src/lib/server/db/user.js
index c64f724..6844d1f 100644
--- a/src/lib/server/db/user.js
+++ b/src/lib/server/db/user.js
@@ -1,5 +1,5 @@
import { createCache } from '$lib/cache.server';
-import { cacheUpdater, cachedMethod } from './root';
+import { cacheUpdater, cachedMethod, refExtendCachedMethod } from './root';
const cache = createCache();
@@ -12,6 +12,18 @@ const cache = createCache();
* @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} users
* @returns {Result}
@@ -25,13 +37,15 @@ const updateUserCache = cacheUpdater(cache);
*/
export const getUsersCached = cachedMethod(cache, getUsers);
+export const getUsersCachedByRef = refExtendCachedMethod(getUsersCached);
+
/**
* @param {import('postgres').Sql} sql
* @param {number[]} user_ids
* @returns {Promise>}
*/
export async function getUsers(sql, user_ids) {
- if (user_ids.length == 0) return {};
+ if (user_ids.length == 0) return new Map();
const query = sql`
SELECT id, username, join_time
@@ -43,14 +57,10 @@ export async function getUsers(sql, user_ids) {
/**
* @type {Result}
*/
- let result = {};
+ let result = new Map();
users.forEach(row => {
- result[row['id']] = {
- id: row['id'],
- name: row['username'],
- join_date: row['join_time']
- }
+ result.set(row['id'], parseUserFromRow(row));
})
return updateUserCache(result);
@@ -64,12 +74,8 @@ export async function getUsers(sql, user_ids) {
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];
+ return users.get(user_id) || {
+ error: true,
+ msg: `Could not find user of ID ${user_id}`
+ };
}
diff --git a/src/routes/(app)/posts/+page.server.js b/src/routes/(app)/posts/+page.server.js
index 66312b8..6c4b64c 100644
--- a/src/routes/(app)/posts/+page.server.js
+++ b/src/routes/(app)/posts/+page.server.js
@@ -5,8 +5,6 @@ import { getPosts } from '$lib/server/db/post';
export async function load({ locals }) {
let result = await getPosts(locals.sql);
- console.log(result);
-
return {
posts: result
};
diff --git a/src/routes/(app)/posts/[id]/+page.server.js b/src/routes/(app)/posts/[id]/+page.server.js
index cebdf3b..7be79e2 100644
--- a/src/routes/(app)/posts/[id]/+page.server.js
+++ b/src/routes/(app)/posts/[id]/+page.server.js
@@ -1,11 +1,18 @@
+import { getCommentsForPost } from "$lib/server/db/comment";
import { getPost } from "$lib/server/db/post";
/** @type {import("@sveltejs/kit").ServerLoad} */
export async function load({ params, locals }) {
- /** @type {import("$types/base").Post | import("$types/error").Error} */
- const post = await getPost(locals.sql, Number(params.id));
+ const post_id = Number(params.id);
+
+ const post = await getPost(locals.sql, post_id);
+
+ const comments = await getCommentsForPost(locals.sql, post_id);
+
+ console.log(comments);
return {
- post: post
+ post: post,
+ comments: comments
};
}
diff --git a/src/routes/(app)/posts/[id]/+page.svelte b/src/routes/(app)/posts/[id]/+page.svelte
index 7fd84a1..9e6a6be 100644
--- a/src/routes/(app)/posts/[id]/+page.svelte
+++ b/src/routes/(app)/posts/[id]/+page.svelte
@@ -1,12 +1,29 @@
{data.post.name}
{data.post.author?.name}
{data.post.content}
+
+ {#each commentTree as reply}
+
+ {/each}
+
diff --git a/src/types/base.ts b/src/types/base.ts
index b278c70..ab74ab8 100644
--- a/src/types/base.ts
+++ b/src/types/base.ts
@@ -1,9 +1,9 @@
-export type Result = {[id: number]: T};
+export type Result = Map;
export type User = {
id: number,
name: string,
- join_date: Date
+ joinDate: Date
};
export type Rating = {
@@ -22,6 +22,20 @@ export type Post = {
name: string,
category: Category,
content: string,
- post_date: Date,
+ postDate: Date,
rating: Rating
};
+
+export type Comment = {
+ id: number,
+ author: User | null,
+ content: string,
+ commentDate: Date,
+ rating: Rating,
+ parentCommentId: number
+};
+
+export type CommentTreeNode = {
+ parent: Comment,
+ children: (CommentTreeNode | number)[]
+};