diff --git a/api/main.ts b/api/main.ts index 358b207..c24a3ec 100644 --- a/api/main.ts +++ b/api/main.ts @@ -1,7 +1,7 @@ /// -/** +/** * @author Esad Mustafoski - * @description Main API file, Handles all the routing/api stuff + * Main API file, Handles all the routing/api stuff */ // +++ IMPORTS ------------------------------------------------------ // @@ -9,12 +9,10 @@ import { Application, Router } from "https://deno.land/x/oak/mod.ts"; import { oakCors } from "https://deno.land/x/cors/mod.ts"; import * as db_utils from "../database/utils.ts"; - // +++ VARIABLES ---------------------------------------------------- // const router = new Router(); const app = new Application(); - // +++ ROUTER ------------------------------------------------------- // // Creates the routes for the API server. // Example: localhost:8000/api will show "testAPIPoint" @@ -36,10 +34,9 @@ router ctx.response.body = { getPosts, countedPosts }; }); - app.use(oakCors()); app.use(router.routes()); app.use(router.allowedMethods()); export { app }; -await app.listen({ port: 8000 }); \ No newline at end of file +await app.listen({ port: 8000 }); diff --git a/database/create_db.ts b/database/create_db.ts index cfc468b..c39a4b2 100644 --- a/database/create_db.ts +++ b/database/create_db.ts @@ -1,5 +1,75 @@ -/// -/** +/** * @author Esad Mustafoski * @description This file is responsible for creating the database and the tables - */ \ No newline at end of file + */ + +/// + +// +++ IMPORTS ------------------------------------------------------ // +import { DB } from "https://deno.land/x/sqlite/mod.ts"; +import { dirname, fromFileUrl, join } from "https://deno.land/std/path/mod.ts"; + +// +++ VARIABLES ---------------------------------------------------- // +const _dirname: string = dirname(fromFileUrl(import.meta.url)); +const dbPath: string = join(_dirname, "../database/esp-projekt.sqlite"); +const db = new DB(dbPath); + +db.execute(` + CREATE TABLE IF NOT EXISTS accounts ( + user_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_group TEXT, + bio TEXT, + displayname TEXT, + username TEXT, + user_email TEXT, + password TEXT, + firstname TEXT, + surname TEXT, + account_created TEXT, + blocked_users TEXT, + followers TEXT, + following TEXT, + contacts TEXT + ) + + CREATE TABLE IF NOT EXISTS posts ( + posts_uuid INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + created_at TEXT, + post_text TEXT, + likes INTEGER, + comments INTEGER + ) + + CREATE TABLE IF NOT EXISTS comments ( + comment_id INTEGER PRIMARY KEY AUTOINCREMENT, + post_id INTEGER, + user_id INTEGER, + created_at TEXT, + comment_text TEXT + ) +`); + +// Sample data generated using online generators +export function insertSampleData(): void { + db.query(`INSERT INTO accounts (user_group, bio, displayname, username, user_email, password, firstname, surname, account_created, blocked_users, followers, following, contacts) VALUES + ('admin', 'Admin bio', 'Admin User', 'admin', 'admin@example.com', 'hashedpass1', 'Admin', 'User', '2024-01-01', '[]', '[]', '[]', '[]'), + ('user', 'I love coding!', 'John Dev', 'johndev', 'john@example.com', 'hashedpass2', 'John', 'Smith', '2024-01-02', '[]', '[3,4]', '[1,2]', '[]'), + ('user', 'Photography enthusiast', 'Alice', 'alice_photo', 'alice@example.com', 'hashedpass3', 'Alice', 'Johnson', '2024-01-03', '[5]', '[1]', '[2]', '[]') + `); + + db.query(`INSERT INTO posts (user_id, created_at, post_text, likes, comments) VALUES + (1, '2024-01-15 10:00:00', 'First post about programming!', 5, 2), + (1, '2024-01-15 11:30:00', 'Check out this new feature', 10, 3), + (2, '2024-01-16 09:15:00', 'Just learned about TypeScript', 8, 1), + (3, '2024-01-16 14:20:00', 'Posted my new photo collection', 15, 4) + `); + + db.query(`INSERT INTO comments (post_id, user_id, created_at, comment_text) VALUES + (1, 2, '2024-01-15 10:05:00', 'Great post!'), + (1, 3, '2024-01-15 10:10:00', 'Very informative'), + (2, 3, '2024-01-15 11:35:00', 'Nice feature'), + (3, 1, '2024-01-16 09:20:00', 'TypeScript is awesome'), + (4, 2, '2024-01-16 14:25:00', 'Beautiful photos!') + `); +}; \ No newline at end of file diff --git a/database/utils.ts b/database/utils.ts index ce3ae15..01f050d 100644 --- a/database/utils.ts +++ b/database/utils.ts @@ -1,18 +1,19 @@ -/** +/** * @author Esad Mustafoski * @description This file is responsible for creating Functions to easily access the Database, Intended for use in the API */ /// -import { DB } from "https://deno.land/x/sqlite/mod.ts"; +// +++ IMPORTS ------------------------------------------------------ // +import { DB, Row } from "https://deno.land/x/sqlite/mod.ts"; import { dirname, fromFileUrl, join } from "https://deno.land/std/path/mod.ts"; +// +++ VARIABLES ---------------------------------------------------- // // __dirname Is never getting used again, It's only needed because the DB Import // from SQLite doesn't like relative paths, so I use this as // A Workaround -// +++ VARIABLES ---------------------------------------------------- // const _dirname: string = dirname(fromFileUrl(import.meta.url)); const dbPath: string = join(_dirname, "../database/esp-projekt.sqlite"); const db = new DB(dbPath); @@ -45,17 +46,35 @@ interface Accounts { contacts: string; } +interface Comments { + comment_id: number; + post_id: number; + author_user_id: number; + date_created_at: string; + text: string; + likes: number; +} + + // +++ FUNCTIONS---------------------------------------------------- // /** + * @param user_uuid The UUID of the User to get the Posts for. Not required + * @description If no user_uuid is provided, all Posts will be returned + * @description If a user_uuid is provided, only the Posts from that User will be returned * @returns Array of all Posts in the Database */ -async function getPostsFromDB() { +async function getPostsFromDB(user_uuid?: string): Promise { const data_result: Array = []; - try { - const rows = await db.query(`SELECT * FROM posts`); + let rows: Row[]; + + try { + if (user_uuid === undefined) { + rows = await db.query(`SELECT * FROM posts`); + } else { + rows = await db.query(`SELECT * FROM posts WHERE user_id = ${user_uuid}`); + } - // Assuming `db.query` returns an array of arrays or tuples for (const row of rows) { const [ posts_uuid, @@ -67,12 +86,12 @@ async function getPostsFromDB() { ] = row; data_result.push({ - posts_uuid: Number(posts_uuid), // Convert to string if necessary + posts_uuid: Number(posts_uuid), user_id: Number(user_id), - created_at: String(created_at), // Convert to Date if necessary + created_at: String(created_at), post_text: String(post_text), - likes: Number(likes), // Convert to number if necessary - comments: Number(comments), // Convert to number if necessary + likes: Number(likes), + comments: Number(comments), }); } } catch (error) { @@ -84,7 +103,7 @@ async function getPostsFromDB() { /** * @returns Array of all Users in the Database */ -async function getAllUsersFromDB() { +async function getAllUsersFromDB(): Promise { const accounts_list: Array = []; try { const rows = await db.query("SELECT * FROM accounts"); @@ -131,6 +150,7 @@ async function getAllUsersFromDB() { // Test Function, not useful // Promise needed because of "await" +// It indicates that the function resolves something async function countPosts(): Promise { let count = 0; try { @@ -145,10 +165,47 @@ async function countPosts(): Promise { } /** - * @param post_id The ID of the Post to get the Comments for + * @param post_id The ID of the Post to get the Comments for. Not required + * @description If no post_id is provided, all Comments will be returned + * @description If a post_id is provided, only the Comments for that Post will be returned * @returns Array of Comments for the Post, or an empty Array if there are no Comments */ -function getCommentsForPost(post_id: number) { +async function getCommentsFromDB(post_id?: number): Promise { + const data_result:Array = []; + let rows: Row[] = []; + + try { + if (post_id === undefined) { + rows = await db.query(`SELECT * FROM comments`); + } else { + rows = await db.query(`SELECT * FROM comments WHERE post_id = ${post_id}`); + } + + for (const row of rows) { + const [ + comment_id, + post_id, + author_user_id, + date_created_at, + text, + likes, + ] = row; + + data_result.push({ + comment_id: Number(comment_id), + post_id: Number(post_id), + author_user_id: Number(author_user_id), + date_created_at: String(date_created_at), + text: String(text), + likes: Number(likes), + }); + } + } catch (error) { + console.error("Error fetching comments", error, "Post ID:", post_id); + return []; + } + + return data_result; } /** @@ -160,18 +217,16 @@ function getCommentsForComments(comment_id: number) { /** * @param user_id The ID of the User to get - * @returns The User with the given ID, or null if the User doesn't exist + * @returns All of an users Data from the Database + * Included: Comments, Posts... Everything including the specific user_uuid in the database */ -function getUserInfoByID(user_id: number) { +function getAllUserInfoByID(user_id: number) { } /** * @param user_id The ID of the User to get the Posts for * @returns Array of Posts from the User, or an empty Array if the User doesn't exist or has no Posts */ -function getAllPostsFromUser(user_id: number) { -} - // Filter Functions function filterForImagePosts(posts_to_filter: Array) { return []; @@ -185,16 +240,15 @@ function filterForTextPosts() { return []; } -// Export all Functions to make this a module +// Exporting all functions as this is a module export { countPosts, filterForImagePosts, filterForTextPosts, filterForVideoPosts, - getAllPostsFromUser, getAllUsersFromDB, getCommentsForComments, - getCommentsForPost, + getCommentsFromDB, getPostsFromDB, - getUserInfoByID, + getAllUserInfoByID, }; diff --git a/tests/api.test.ts b/tests/api.test.ts index d94a1bf..b8cccab 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -1,11 +1,16 @@ -// Responsible: Esad Mustafoski +/** + * @author Esad Mustafoski + * @file test/api_main_test.ts + * This file is made to test the API returns, Is not ready yet + */ -// test/api_main_test.ts /// -// GENERATED USING AI, DO NOT USE YET + +// +++ IMPORTS ------------------------------------------------------ // import { superoak } from "https://deno.land/x/superoak/mod.ts"; import { app } from "../api/main.ts"; +// +++ TESTS ------------------------------------------------------- // Deno.test("GET /api returns testAPIPoint", async () => { const request = await superoak(app); await request.get("/api").expect(200).expect("testAPIPoint"); diff --git a/tests/db.test.ts b/tests/db.test.ts index e98462e..99aa6af 100644 --- a/tests/db.test.ts +++ b/tests/db.test.ts @@ -1,7 +1,12 @@ -// Responsible: Esad Mustafoski +/** + * @author Esad Mustafoski + * @file db.test.ts + * This file is made to test the Database Functions + */ /// -// GENERATED USING AI, DO NOT USE YET + +// +++ IMPORTS ------------------------------------------------------ // import { assert, assertEquals, @@ -10,6 +15,7 @@ import { import * as db_utils from "../database/utils.ts"; +// +++ TESTS ------------------------------------------------------- // // Database Tests Deno.test("Database Connection", async () => { const users = await db_utils.getAllUsersFromDB(); @@ -20,12 +26,15 @@ Deno.test("Database Connection", async () => { Deno.test("getAllUsersFromDB returns array of users with correct properties", async () => { const users = await db_utils.getAllUsersFromDB(); assert(Array.isArray(users), "Expected users to be an array"); - + if (users.length > 0) { - const user = users[0]; - assert(typeof user.user_id === "number", "user_id should be a number"); - assert(typeof user.username === "string", "username should be a string"); - assert(typeof user.user_email === "string", "user_email should be a string"); + const user = users[0]; + assert(typeof user.user_id === "number", "user_id should be a number"); + assert(typeof user.username === "string", "username should be a string"); + assert( + typeof user.user_email === "string", + "user_email should be a string", + ); } }); @@ -33,13 +42,16 @@ Deno.test("getAllUsersFromDB returns array of users with correct properties", as Deno.test("getPostsFromDB returns array of posts with correct structure", async () => { const posts = await db_utils.getPostsFromDB(); assert(Array.isArray(posts), "Expected posts to be an array"); - + if (posts.length > 0) { - const post = posts[0]; - assert(typeof post.posts_uuid === "number", "posts_uuid should be a number"); - assert(typeof post.user_id === "number", "user_id should be a number"); - assert(typeof post.post_text === "string", "post_text should be a string"); - assert(typeof post.likes === "number", "likes should be a number"); + const post = posts[0]; + assert( + typeof post.posts_uuid === "number", + "posts_uuid should be a number", + ); + assert(typeof post.user_id === "number", "user_id should be a number"); + assert(typeof post.post_text === "string", "post_text should be a string"); + assert(typeof post.likes === "number", "likes should be a number"); } }); @@ -47,12 +59,12 @@ Deno.test("countPosts returns valid number", async () => { const count = await db_utils.countPosts(); assert(typeof count === "number", "Count should be a number"); assert(count >= 0, "Count should be non-negative"); - + const posts = await db_utils.getPostsFromDB(); assertEquals(count, posts.length, "Count should match number of posts"); }); -// Filter Tests +// Filter Tests (Will not work until the functions are implemented) Deno.test("filterForImagePosts returns array", () => { const mockPosts: Array = []; const result = db_utils.filterForImagePosts(mockPosts); @@ -81,43 +93,47 @@ Deno.test("getPostsFromDB handles errors gracefully", async () => { }); // Comments Tests -Deno.test("getCommentsForPost handles invalid post_id", () => { - const result = db_utils.getCommentsForPost(-1); - assertEquals(result, undefined, "Should handle invalid post_id"); +Deno.test("getCommentsFromDB handles invalid post_id", async () => { + const result = await db_utils.getCommentsFromDB(-1); + assertEquals(result, [], "Should handle invalid post_id"); }); -Deno.test("getCommentsForComments handles invalid comment_id", () => { +Deno.test("getCommentsFromDB handles invalid comment_id", () => { const result = db_utils.getCommentsForComments(-1); assertEquals(result, undefined, "Should handle invalid comment_id"); }); // User Info Tests Deno.test("getUserInfoByID handles invalid user_id", () => { - const result = db_utils.getUserInfoByID(-1); + const result = db_utils.getAllUserInfoByID(-1); assertEquals(result, undefined, "Should handle invalid user_id"); }); -Deno.test("getAllPostsFromUser handles invalid user_id", () => { - const result = db_utils.getAllPostsFromUser(-1); - assertEquals(result, undefined, "Should handle invalid user_id"); +Deno.test("getPostsFromDB handles invalid user_id", async () => { + const result = await db_utils.getPostsFromDB("invalid"); + assertEquals(result, [], "Should handle invalid user_id"); }); // Data Validation Tests Deno.test("User data types are correct", async () => { const users = await db_utils.getAllUsersFromDB(); if (users.length > 0) { - const user = users[0]; - assertMatch(user.user_email, /^[^\s@]+@[^\s@]+\.[^\s@]+$/, "Email should be valid format"); - assert(user.password.length >= 1, "Password should not be empty"); + const user = users[0]; + assertMatch( + user.user_email, + /^[^\s@]+@[^\s@]+\.[^\s@]+$/, + "Email should be valid format", + ); + assert(user.password.length >= 1, "Password should not be empty"); } }); Deno.test("Post data types are correct", async () => { const posts = await db_utils.getPostsFromDB(); if (posts.length > 0) { - const post = posts[0]; - assert(post.likes >= 0, "Likes should be non-negative"); - assert(post.comments >= 0, "Comments should be non-negative"); - assert(post.post_text.length > 0, "Post text should not be empty"); + const post = posts[0]; + assert(post.likes >= 0, "Likes should be non-negative"); + assert(post.comments >= 0, "Comments should be non-negative"); + assert(post.post_text.length > 0, "Post text should not be empty"); } -}); \ No newline at end of file +});