All checks were successful
Pull Request CI / Merge Check (pull_request) Successful in 5m18s
150 lines
5.0 KiB
TypeScript
150 lines
5.0 KiB
TypeScript
import { toDateTime } from "@app/shared/utils/time";
|
|
import pool from "../../db";
|
|
import { LOARequest, LOAType } from '@app/shared/types/loa'
|
|
import { PagedData } from '@app/shared/types/pagination'
|
|
import { DiscussionPost } from '@app/shared/types/discussion';
|
|
import { DiscussionComment } from '@app/shared/types/discussion';
|
|
|
|
/**
|
|
* Retrieves all discussion posts with pagination and optional type filtering.
|
|
* @template T - The type of content stored in discussion posts
|
|
* @param {string} [type] - Optional type filter to retrieve only posts of a specific type
|
|
* @param {number} [page=1] - The page number for pagination (1-indexed)
|
|
* @param {number} [pageSize=10] - The number of posts per page
|
|
* @returns {Promise<PagedData<DiscussionPost<T>>>} A promise that resolves to paginated discussion posts with metadata
|
|
* @throws {Error} If the database query fails
|
|
*/
|
|
export async function getAllDiscussions<T>(type?: string, page = 1, pageSize = 10, search?: string): Promise<PagedData<DiscussionPost<T>>> {
|
|
const offset = (page - 1) * pageSize;
|
|
const params: any[] = [];
|
|
|
|
// Base query parts
|
|
let whereClause = "WHERE is_deleted = FALSE";
|
|
if (type) {
|
|
whereClause += " AND type = ?";
|
|
params.push(type);
|
|
}
|
|
|
|
const sql = `
|
|
SELECT
|
|
p.*,
|
|
m.name as poster_name
|
|
FROM discussion_posts AS p
|
|
LEFT JOIN members m ON p.poster_id = m.id
|
|
${whereClause}
|
|
ORDER BY
|
|
p.is_open DESC, -- Show active/unlocked threads first
|
|
p.created_at DESC -- Then show newest first
|
|
LIMIT ? OFFSET ?;
|
|
`;
|
|
|
|
// Add pagination params to the end
|
|
params.push(pageSize, offset);
|
|
|
|
// Execute queries
|
|
const posts: DiscussionPost<T>[] = await pool.query(sql, params) as DiscussionPost<T>[];
|
|
|
|
// Get count for the specific types
|
|
const countSql = `SELECT COUNT(*) as count FROM discussion_posts ${whereClause}`;
|
|
const countResult = await pool.query(countSql, type ? [type] : []);
|
|
const totalCount = Number(countResult[0].count);
|
|
|
|
const totalPages = Math.ceil(totalCount / pageSize);
|
|
|
|
return {
|
|
data: posts,
|
|
pagination: {
|
|
page,
|
|
pageSize,
|
|
total: totalCount,
|
|
totalPages
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a new discussion post.
|
|
* @template T - The type of content for the discussion post
|
|
* @param {string} type - The type/category of the discussion post
|
|
* @param {number} authorID - The ID of the member creating the post
|
|
* @param {postTitle} string - The title of the discussion post
|
|
* @param {T} data - The content data to be stored in the post
|
|
* @returns {Promise<Number>} A promise that resolves to the ID of the newly created post
|
|
* @throws {Error} If the database insertion fails
|
|
*/
|
|
export async function createDiscussion<T>(type: string, authorID: number, postTitle: string, data: T): Promise<number> {
|
|
const sql = `
|
|
INSERT INTO discussion_posts (type, poster_id, title, content)
|
|
VALUES (?, ?, ?, ?)
|
|
`;
|
|
|
|
console.log(data);
|
|
const result = await pool.query(sql, [
|
|
type,
|
|
authorID,
|
|
postTitle,
|
|
JSON.stringify(data)
|
|
]);
|
|
|
|
console.log(result);
|
|
return Number(result.insertId);
|
|
}
|
|
|
|
/**
|
|
* Retrieve a single discussion post by its ID.
|
|
* @template T - type of the content stored in the post (e.g. ModRequest)
|
|
* @param {number} id - The id of the discussion post to fetch
|
|
* @returns {Promise<DiscussionPost<T> | null>} The discussion post or null if not found
|
|
*/
|
|
export async function getDiscussionById<T>(id: number): Promise<DiscussionPost<T> | null> {
|
|
// Get the post
|
|
const postSql = `
|
|
SELECT
|
|
p.*,
|
|
m.name as poster_name
|
|
FROM discussion_posts AS p
|
|
LEFT JOIN members m ON p.poster_id = m.id
|
|
WHERE p.id = ?
|
|
LIMIT 1;
|
|
`;
|
|
const postResults = (await pool.query(postSql, [id])) as DiscussionPost<T>[];
|
|
if (postResults.length === 0) {
|
|
return null;
|
|
}
|
|
const post = postResults[0];
|
|
|
|
// Get comments for the post
|
|
const commentSql = `
|
|
SELECT
|
|
c.*
|
|
FROM discussion_comments AS c
|
|
WHERE c.post_id = ?
|
|
AND c.is_deleted = FALSE
|
|
ORDER BY c.created_at ASC;
|
|
`;
|
|
const comments = (await pool.query(commentSql, [id])) as DiscussionComment[];
|
|
|
|
// Attach comments to post
|
|
post.comments = comments;
|
|
|
|
return post;
|
|
}
|
|
|
|
export async function getPostComments(postID: number): Promise<DiscussionComment[]> {
|
|
let comments = await pool.query("SELECT * FROM discussion_comments WHERE post_id = ?", [postID]);
|
|
return comments;
|
|
}
|
|
|
|
export async function postComment(commentData: DiscussionComment, poster: number) {
|
|
const sql = `
|
|
INSERT INTO discussion_comments (post_id, poster_id, content) VALUES (?, ?, ?);
|
|
`;
|
|
|
|
const result = await pool.query(sql, [commentData.post_id, poster, commentData.content]);
|
|
|
|
if (!result.affectedRows || result.affectedRows !== 1) {
|
|
throw new Error('Failed to insert comment: expected 1 row to be inserted');
|
|
}
|
|
|
|
return Number(result.insertId);
|
|
} |