Merged API and Databank into Main branch

This commit is contained in:
Lynixenn
2025-03-30 22:02:56 +02:00
parent 3cd92d7fff
commit fb916e9e9f
19 changed files with 2316 additions and 0 deletions

499
api/doc/API.md Normal file
View File

@@ -0,0 +1,499 @@
# This was generated using AI!!! Will edit if errors are found.
# API Documentation
## Index
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/` | Returns basic information and directions for using the API endpoints. |
| GET | `/api` | Redirects to the full API documentation. |
---
## Account Endpoints
| Method | Endpoint | Description |
|--------|-------------------------------------------|----------------------------------------------------------------|
| POST | `/api/account/login` | Logs in a user with username and password. |
| POST | `/api/account/register` | Registers a new user with required details. |
| POST | `/api/account/logout` | Logs a user out. (TODO) |
| POST | `/api/account/password/forgot` | Initiates password recovery. (TODO) |
| POST | `/api/account/password/reset` | Resets the password. (TODO) |
| POST | `/api/account/password/change` | Changes the password. (TODO) |
| POST | `/api/account/email/change-email` | Changes the user's email address. (TODO) |
| POST | `/api/account/email/verify-email` | Verifies a user's email. (TODO) |
| POST | `/api/account/delete-account` | Deletes the user account. (TODO) |
| POST | `/api/account/block` | Blocks a user account. (TODO) |
---
## Auth Endpoints
| Method | Endpoint | Description |
|--------|----------------------|--------------------------------------------------|
| GET | `/api/auth` | Auth endpoint (placeholder). |
| GET | `/api/auth/verify` | Verifies an authentication token. (TODO) |
| GET | `/api/auth/refresh` | Refreshes an authentication token. (TODO) |
---
## User Endpoints
| Method | Endpoint | Description |
|--------|---------------|------------------------------------------------------------|
| GET | `/api/users` | Retrieves a list of all users from the database. |
---
## Chat Endpoints
| Method | Endpoint | Description |
|--------|----------------------------------|-----------------------------------------------------------------|
| GET | `/api/chats` | Retrieves chats for a user (requires an Authorization header). |
| GET | `/api/chat/:id` | Retrieves details of a specific chat using its ID. |
| POST | `/api/chat/create` | Creates a new chat with at least 2 participants. |
| POST | `/api/chat/:id/message` | Sends a message to the specified chat. |
| DELETE | `/api/chat/:id` | Deletes a specific chat by its ID. |
---
## Post Endpoints
| Method | Endpoint | Description |
|--------|----------------------------------|-------------------------------------------------------------------|
| GET | `/api/posts` | Retrieves all posts. |
| GET | `/api/post/:id` | Retrieves a specific post by its ID. |
| POST | `/api/post/create` | Creates a new post. |
| PUT | `/api/post/:id` | Updates an existing post (requires at least one update field). |
| DELETE | `/api/post/:id` | Deletes a specific post by its ID. |
| POST | `/api/post/:id/like` | Likes a specific post. |
---
## Comment Endpoints
| Method | Endpoint | Description |
|--------|-----------------------------------|--------------------------------------------------------------------|
| GET | `/api/post/:id/comments` | Retrieves all comments associated with a specific post. |
| POST | `/api/post/:id/comment` | Adds a new comment to a specified post. |
| PUT | `/api/comment/:id` | Updates an existing comment using its ID. |
| DELETE | `/api/comment/:id` | Deletes a specific comment by its ID. |
| POST | `/api/comment/:id/like` | Likes a specific comment. |
> Note: Replace any `:id` placeholder in the URLs with the actual identifier when making requests.
> Note: Endpoints marked as TODO are placeholders for future implementation.
---
## Base URL
Assuming the server is running on port 8000, the base URL is:
```
http://localhost:8000
```
---
## Index & Documentation Routes
- **GET /**
A simple informational endpoint.
- **Response:** `"For endpoints, use /api/{name}"`
**Example:**
```bash
curl http://localhost:8000/
```
- **GET /api**
Directs users for API documentation.
- **Response:** `"For API Documentation, visit /docs"`
**Example:**
```bash
curl http://localhost:8000/api
```
---
## Account Endpoints
### 1. POST /api/account/login
Log in a user with their username and password.
- **Request Body:**
```json
{
"username": "johndoe",
"password": "secret123"
}
```
- **Response:** On success returns status 200 with body `"Success"`. If unsuccessful, appropriate error messages are returned.
**Example:**
```bash
curl -X POST http://localhost:8000/api/account/login \
-H "Content-Type: application/json" \
-d '{"username": "johndoe", "password": "secret123"}'
```
### 2. POST /api/account/register
Register a new user with all required fields.
- **Request Body:**
```json
{
"username": "johndoe",
"password": "secret123",
"userGroup": "default",
"displayname": "John Doe",
"user_email": "john@example.com",
"firstname": "John",
"surname": "Doe"
}
```
- **Response:** On success returns a status of 200 and a message indicating the user ID registration.
**Example:**
```bash
curl -X POST http://localhost:8000/api/account/register \
-H "Content-Type: application/json" \
-d '{
"username": "johndoe",
"password": "secret123",
"userGroup": "default",
"displayname": "John Doe",
"user_email": "john@example.com",
"firstname": "John",
"surname": "Doe"
}'
```
### Other Account Endpoints
- POST `/api/account/logout`
- POST `/api/account/password/forgot`
- POST `/api/account/password/reset`
- POST `/api/account/password/change`
- POST `/api/account/email/change-email`
- POST `/api/account/email/verify-email`
- POST `/api/account/delete-account`
- POST `/api/account/block`
These endpoints are marked as TODO and currently have no implemented logic.
---
## Auth Endpoints
- GET `/api/auth`
- GET `/api/auth/verify`
- GET `/api/auth/refresh`
These endpoints are also marked as TODO.
---
## User Endpoints
### GET /api/users
Retrieve all users from the database.
- **Response:** JSON array of user objects.
**Example:**
```bash
curl http://localhost:8000/api/users
```
---
## Chat Endpoints
### 1. GET /api/chats
Retrieve all chats for a user. Requires an "Authorization" header and an optional query parameter for userId.
- **Headers:**
- Authorization: Bearer <token> (only required to simulate authentication)
- **Query Parameter:**
- userId (optional; defaults to "1" if not provided)
- **Response:** JSON array of chats for the specified user.
**Example:**
```bash
curl "http://localhost:8000/api/chats?userId=2" \
-H "Authorization: Bearer mytoken123"
```
### 2. GET /api/chat/:id
Retrieve details of a specific chat identified by its ID.
- **Route Parameter:**
- id: Chat ID
- **Response:** JSON object containing chat details.
**Example:**
```bash
curl http://localhost:8000/api/chat/10
```
### 3. POST /api/chat/create
Create a new chat with at least two participants.
- **Request Body:**
```json
{
"participants": [1, 2],
"chatName": "Group Chat"
}
```
- **Response:** Returns status 201 and a JSON object with the newly created "chatId".
**Example:**
```bash
curl -X POST http://localhost:8000/api/chat/create \
-H "Content-Type: application/json" \
-d '{"participants": [1, 2], "chatName": "Group Chat"}'
```
### 4. POST /api/chat/:id/message
Send a message to a specific chat.
- **Route Parameter:**
- id: Chat ID
- **Request Body:**
```json
{
"senderId": 1,
"content": "Hello, world!"
}
```
- **Response:** Returns status 201 and a JSON object with the "messageId" of the new message.
**Example:**
```bash
curl -X POST http://localhost:8000/api/chat/10/message \
-H "Content-Type: application/json" \
-d '{"senderId": 1, "content": "Hello, world!"}'
```
### 5. DELETE /api/chat/:id
Delete a specific chat.
- **Route Parameter:**
- id: Chat ID
- **Response:** Returns status 200 with a confirmation message.
**Example:**
```bash
curl -X DELETE http://localhost:8000/api/chat/10
```
---
## Post Endpoints
### 1. GET /api/posts
Retrieve all posts.
- **Response:** JSON array of posts.
**Example:**
```bash
curl http://localhost:8000/api/posts
```
### 2. GET /api/post/:id
Retrieve a specific post by its ID.
- **Route Parameter:**
- id: Post ID
- **Response:** JSON object representing the post.
**Example:**
```bash
curl http://localhost:8000/api/post/15
```
### 3. POST /api/post/create
Create a new post.
- **Request Body:**
```json
{
"userId": 1,
"postText": "This is a new post",
"postType": "text"
}
```
- **Response:** Returns status 201 with the "postId" of the created post.
**Example:**
```bash
curl -X POST http://localhost:8000/api/post/create \
-H "Content-Type: application/json" \
-d '{"userId": 1, "postText": "This is a new post", "postType": "text"}'
```
### 4. PUT /api/post/:id
Update an existing post. At least one of `postText` or `postType` must be provided.
- **Route Parameter:**
- id: Post ID
- **Request Body:**
```json
{
"postText": "Updated post text",
"postType": "text"
}
```
- **Response:** Returns status 200 with a confirmation message.
**Example:**
```bash
curl -X PUT http://localhost:8000/api/post/15 \
-H "Content-Type: application/json" \
-d '{"postText": "Updated post text", "postType": "text"}'
```
### 5. DELETE /api/post/:id
Delete a specific post.
- **Route Parameter:**
- id: Post ID
- **Response:** Returns status 200 with a message confirming deletion.
**Example:**
```bash
curl -X DELETE http://localhost:8000/api/post/15
```
### 6. POST /api/post/:id/like
Like a specific post.
- **Route Parameter:**
- id: Post ID
- **Request Body:**
```json
{
"userId": 1
}
```
- **Response:** Returns status 200 with a confirmation message.
**Example:**
```bash
curl -X POST http://localhost:8000/api/post/15/like \
-H "Content-Type: application/json" \
-d '{"userId": 1}'
```
---
## Comment Endpoints
### 1. GET /api/post/:id/comments
Retrieve all comments for a specific post.
- **Route Parameter:**
- id: Post ID
- **Response:** JSON array of comments.
**Example:**
```bash
curl http://localhost:8000/api/post/15/comments
```
### 2. POST /api/post/:id/comment
Create a new comment on a specific post.
- **Route Parameter:**
- id: Post ID
- **Request Body:**
```json
{
"userId": 1,
"text": "This is a comment"
}
```
- **Response:** Returns status 201 with the newly created "commentId".
**Example:**
```bash
curl -X POST http://localhost:8000/api/post/15/comment \
-H "Content-Type: application/json" \
-d '{"userId": 1, "text": "This is a comment"}'
```
### 3. PUT /api/comment/:id
Update an existing comment.
- **Route Parameter:**
- id: Comment ID
- **Request Body:**
```json
{
"text": "Updated comment text"
}
```
- **Response:** Returns status 200 with a confirmation message.
**Example:**
```bash
curl -X PUT http://localhost:8000/api/comment/20 \
-H "Content-Type: application/json" \
-d '{"text": "Updated comment text"}'
```
### 4. DELETE /api/comment/:id
Delete a specific comment.
- **Route Parameter:**
- id: Comment ID
- **Response:** Returns status 200 with a confirmation message.
**Example:**
```bash
curl -X DELETE http://localhost:8000/api/comment/20
```
### 5. POST /api/comment/:id/like
Like a specific comment.
- **Route Parameter:**
- id: Comment ID
- **Request Body:**
```json
{
"userId": 1
}
```
- **Response:** Returns status 200 with a confirmation message.
**Example:**
```bash
curl -X POST http://localhost:8000/api/comment/20/like \
-H "Content-Type: application/json" \
-d '{"userId": 1}'
```

73
api/helpers.ts Normal file
View File

@@ -0,0 +1,73 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @file api/helpers.ts
* @description Helper functions for the API
*/
// +++ IMPORTS ------------------------------------------------------ //
import { Context } from "https://deno.land/x/oak/mod.ts";
import { encodeHex } from "jsr:@std/encoding/hex";
// import { hash } from "node:crypto";
export type ApiResponse = {
status: number;
body: unknown;
};
// +++ FUNCTIONS ---------------------------------------------------- //
/**
* @description Sends a response to the client
* @usage sendResponse(ctx, { status: 200, body: { message: "Success" } })
* Status is the HTTP Status code
* Body is the response body/message/data.
*/
const sendResponse = (ctx: Context, { status, body }: ApiResponse): void => {
ctx.response.status = status;
ctx.response.body = body as any;
};
/**
* @usage errorResponse(ctx, 401, "Unauthorized")
* @see sendResponse
*/
const errorResponse = (ctx: Context, status: number, message: string): void => {
sendResponse(ctx, { status, body: { error: message } });
};
/**
* @description password "Salter", used to salt the passwords before the hash, this salt will be
* returned seperately to save the salt in the DB
* @param password The password to salt
* @returns {saltedPassword: string, salt: string} Password with the salt + Salt seperately, both strings
*/
const saltPassword = async (
password: string,
): Promise<{ saltedPassword: string; salt: string }> => {
const saltBytes = new Uint8Array(16); // 16 bytes = 128 bits for randomness
await crypto.getRandomValues(saltBytes);
const salt = encodeHex(saltBytes);
const saltedPassword = `${password}${salt}`;
return {
saltedPassword,
salt,
};
};
/**
* @description Hashing Function for Passwords/etc
* @param password The password to hash
* @returns {hash: string} The hashed password as a string
*/
const hashPassword = async (password: string): Promise<string> => {
const to_hash = password;
const buffer = new TextEncoder().encode(to_hash);
const hash_buffer = await crypto.subtle.digest("SHA-256", buffer);
const hash = await encodeHex(hash_buffer);
return hash;
};
export { errorResponse, hashPassword, saltPassword, sendResponse };

142
api/helpers/chat_api.ts Normal file
View File

@@ -0,0 +1,142 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description API file for Comments
*/
// +++ IMPORTS ------------------------------------------------------ //
import * as db_utils from "../../database/utils.ts";
import * as helper_utils from "../helpers.ts";
// +++ FUNCTIONS ----------------------------------------------------- //
async function api_getChats(ctx: any): Promise<void> {
try {
const authHeader = ctx.request.headers.get("Authorization");
if (!authHeader) {
helper_utils.errorResponse(ctx, 401, "Authentication Required");
return;
}
// Get userId from query parameter (for testing) or use 1 as default (avoid errors because of userid)
// Assumes 1 is a test account
const userId = ctx.request.url.searchParams.get("userId") || "1";
const chats = await db_utils.getUserChats(userId);
ctx.response.body = chats;
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error retrieving chats");
console.log(error);
}
}
async function api_getChatById(ctx: any): Promise<void> {
try {
const chatId = ctx.params.id;
if (!chatId) {
helper_utils.errorResponse(ctx, 400, "Chat ID required");
return;
}
const chat = await db_utils.getChatById(chatId);
if (!chat) {
helper_utils.errorResponse(ctx, 404, "Chat not found");
return;
}
ctx.response.body = chat;
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error retrieving chat");
console.log(error);
}
}
async function api_createChat(ctx: any): Promise<void> {
try {
const body = ctx.request.body;
const result = await body.json();
const { participants, chatName } = result;
if (
!participants || !Array.isArray(participants) || participants.length < 2
) {
helper_utils.errorResponse(
ctx,
400,
"Two people required to create a chat",
);
return;
}
const chatId = await db_utils.createChat(participants, chatName || "");
helper_utils.sendResponse(ctx, {
status: 201,
body: { chatId },
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error creating chat");
console.log(error);
}
}
async function api_sendMessage(ctx: any): Promise<void> {
try {
const chatId = ctx.params.id;
if (!chatId) {
helper_utils.errorResponse(ctx, 400, "Chat ID required");
return;
}
const body = ctx.request.body;
const result = await body.json();
const { senderId, content } = result;
if (!senderId || !content) {
helper_utils.errorResponse(
ctx,
400,
"Sender ID and message content required",
);
return;
}
const messageId = await db_utils.addMessageToChat(
chatId,
senderId,
content,
);
helper_utils.sendResponse(ctx, {
status: 201,
body: { messageId },
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error sending message");
console.log(error);
}
}
async function api_deleteChat(ctx: any): Promise<void> {
try {
const chatId = ctx.params.id;
if (!chatId) {
helper_utils.errorResponse(ctx, 400, "Chat ID required");
return;
}
await db_utils.deleteChat(chatId);
helper_utils.sendResponse(ctx, {
status: 200,
body: { message: "Chat deleted successfully" },
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error deleting chat");
console.log(error);
}
}
export {
api_createChat,
api_deleteChat,
api_getChatById,
api_getChats,
api_sendMessage,
};

142
api/helpers/comments_api.ts Normal file
View File

@@ -0,0 +1,142 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description API file for Comments
*/
// +++ IMPORTS ------------------------------------------------------ //
import * as db_utils from "../../database/utils.ts";
import * as helper_utils from "../helpers.ts";
// +++ FUNCTIONS ----------------------------------------------------- //
async function api_getPostComments(ctx: any): Promise<void> {
try {
const postId = ctx.params.id;
if (!postId) {
helper_utils.errorResponse(ctx, 400, "Post ID required");
return;
}
const comments = await db_utils.getCommentsFromDB(Number(postId));
ctx.response.body = comments;
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error retrieving comments");
console.log(error);
}
}
async function api_createComment(ctx: any): Promise<void> {
try {
const postId = ctx.params.id;
if (!postId) {
helper_utils.errorResponse(ctx, 400, "Post ID required");
return;
}
const body = ctx.request.body;
const result = await body.json();
const { userId, text } = result;
if (!userId || !text) {
helper_utils.errorResponse(ctx, 400, "User ID and comment text required");
return;
}
// Create timestamp in the format expected by the database
const createdAt = `${Math.floor(Date.now() / 1000)}-${
new Date().toLocaleDateString("en-GB").split("/").join("-")
}`;
const commentId = await db_utils.createComment(postId, userId, createdAt, text);
helper_utils.sendResponse(ctx, {
status: 201,
body: { commentId }
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error creating comment");
console.log(error);
}
}
async function api_updateComment(ctx: any): Promise<void> {
try {
const commentId = ctx.params.id;
if (!commentId) {
helper_utils.errorResponse(ctx, 400, "Comment ID required");
return;
}
const body = ctx.request.body;
const result = await body.json();
const { text } = result;
if (!text) {
helper_utils.errorResponse(ctx, 400, "Comment text required");
return;
}
await db_utils.updateComment(commentId, text);
helper_utils.sendResponse(ctx, {
status: 200,
body: { message: "Comment updated successfully" }
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error updating comment");
console.log(error);
}
}
async function api_deleteComment(ctx: any): Promise<void> {
try {
const commentId = ctx.params.id;
if (!commentId) {
helper_utils.errorResponse(ctx, 400, "Comment ID required");
return;
}
await db_utils.deleteComment(commentId);
helper_utils.sendResponse(ctx, {
status: 200,
body: { message: "Comment deleted successfully" }
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error deleting comment");
console.log(error);
}
}
async function api_likeComment(ctx: any): Promise<void> {
try {
const commentId = ctx.params.id;
if (!commentId) {
helper_utils.errorResponse(ctx, 400, "Comment ID required");
return;
}
const body = ctx.request.body;
const result = await body.json();
const { userId } = result;
if (!userId) {
helper_utils.errorResponse(ctx, 400, "User ID required");
return;
}
await db_utils.likeComment(commentId, userId);
helper_utils.sendResponse(ctx, {
status: 200,
body: { message: "Comment liked successfully" }
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error liking comment");
console.log(error);
}
}
export {
api_createComment,
api_deleteComment,
api_getPostComments,
api_likeComment,
api_updateComment,
};

11
api/helpers/mod.ts Normal file
View File

@@ -0,0 +1,11 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description A mod file is used to export all the functions in the folder, making them easier to access.
* @file mod.ts
*/
export * from "./chat_api.ts";
export * from "./comments_api.ts";
export * from "./post_api.ts";
export * from "./user_api.ts";

147
api/helpers/post_api.ts Normal file
View File

@@ -0,0 +1,147 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description API file for Posts
*/
// +++ IMPORTS ------------------------------------------------------ //
import * as db_utils from "../../database/utils.ts";
import * as helper_utils from "../helpers.ts";
import { Context } from "https://deno.land/x/oak@v17.1.2/mod.ts";
// +++ FUNCTIONS ----------------------------------------------------- //
async function api_getPostById(ctx: any): Promise<void> {
try {
const postId = ctx.params.id;
if (!postId) {
helper_utils.errorResponse(ctx, 400, "Post ID required");
return;
}
const post = await db_utils.getPostById(postId);
if (!post) {
helper_utils.errorResponse(ctx, 404, "Post not found");
return;
}
ctx.response.body = post;
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error retrieving post");
}
}
async function api_createPost(ctx: Context): Promise<void> {
try {
const body = ctx.request.body;
const result = await body.json();
const { userId, postText, postType } = result;
if (!userId || !postText || !postType) {
helper_utils.errorResponse(
ctx,
400,
"User ID, post text, and post type required",
);
return;
}
// Create timestamp in the format expected by the database
const createdAt = `${Math.floor(Date.now() / 1000)}-${new Date()
.toLocaleDateString("en-GB")
.split("/")
.join("-")}`;
const postId = await db_utils.createPost(
userId,
createdAt,
postText,
postType,
);
helper_utils.sendResponse(ctx, {
status: 201,
body: { postId },
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error creating post");
}
}
async function api_updatePost(ctx: any): Promise<void> {
try {
const postId = ctx.params.id;
if (!postId) {
helper_utils.errorResponse(ctx, 400, "Post ID required");
return;
}
const body = ctx.request.body;
const result = await body.json();
const { postText, postType } = result;
if (!postText && !postType) {
helper_utils.errorResponse(ctx, 400, "No update data provided");
return;
}
await db_utils.updatePost(postId, postText, postType);
helper_utils.sendResponse(ctx, {
status: 200,
body: { message: "Post updated successfully" },
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error updating post");
}
}
async function api_deletePost(ctx: any): Promise<void> {
try {
const postId = ctx.params.id;
if (!postId) {
helper_utils.errorResponse(ctx, 400, "Post ID required");
return;
}
await db_utils.deletePost(postId);
helper_utils.sendResponse(ctx, {
status: 200,
body: { message: "Post deleted successfully" },
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error deleting post");
}
}
async function api_likePost(ctx: any): Promise<void> {
try {
const postId = ctx.params.id;
if (!postId) {
helper_utils.errorResponse(ctx, 400, "Post ID required");
return;
}
const body = ctx.request.body;
const result = await body.json();
const { userId } = result;
if (!userId) {
helper_utils.errorResponse(ctx, 400, "User ID required");
return;
}
await db_utils.likePost(postId, userId);
helper_utils.sendResponse(ctx, {
status: 200,
body: { message: "Post liked successfully" },
});
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Error liking post");
}
}
export {
api_createPost,
api_deletePost,
api_getPostById,
api_likePost,
api_updatePost,
};

45
api/helpers/user_api.ts Normal file
View File

@@ -0,0 +1,45 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description API file for Users
*/
// +++ IMPORTS ------------------------------------------------------ //
import * as db_utils from "../../database/utils.ts";
// import * as helper_utils from "../helpers.ts";
import { Context } from "https://deno.land/x/oak@v17.1.2/mod.ts";
// +++ FUNCTIONS ----------------------------------------------------- //
/*async function api_user_getInfo(ctx: any): Promise<void> {
const id = ctx.params.id;
if (!id) {
ctx.response.status = 400;
ctx.response.body = { error: "User ID required" };
return;
}
try {
const user = await db_utils.getAllUserInfoByID(id);
if (user === null || user === undefined) {
helper_utils.errorResponse(ctx, 404, "User not found");
return;
}
ctx.response.body = user;
} catch (error) {
helper_utils.errorResponse(ctx, 500, error as string);
}
}
*/
async function api_getAllUsers(ctx: Context): Promise<void> {
const getUsers = await db_utils.getAllUsersFromDB();
ctx.response.body = getUsers;
}
export {
api_getAllUsers,
// api_user_getInfo
};

307
api/main.ts Normal file
View File

@@ -0,0 +1,307 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description Main API file, Handles all the routing/api stuff
*/
// +++ IMPORTS ------------------------------------------------------ //
import {
Application,
Context,
Next,
Router,
} from "https://deno.land/x/oak@v17.1.2/mod.ts";
import { oakCors } from "https://deno.land/x/cors@v1.2.2/mod.ts";
import {
dirname,
fromFileUrl,
join,
} from "https://deno.land/std@0.224.0/path/mod.ts";
import * as db_utils from "../database/utils.ts";
import * as helper_utils from "./helpers.ts";
import {
// --- Chat --- //
api_createChat,
api_createComment,
api_createPost,
api_deleteChat,
api_deleteComment,
api_deletePost,
// --- User --- //
api_getAllUsers,
api_getChatById,
api_getChats,
// --- Post --- //
api_getPostById,
// --- Comment --- //
api_getPostComments,
api_likeComment,
api_likePost,
api_sendMessage,
api_updateComment,
api_updatePost,
} from "./helpers/mod.ts";
// +++ VARIABLES / TYPES --------------------------------------------- //
const router = new Router();
const app = new Application();
// unused for now
type ApiResponse = {
status: number;
body: unknown;
};
// database creation if missing, runs here because this is the main file executed by the API.
db_utils.ensureDatabaseExists();
// +++ ROUTER ------------------------------------------------------- //
// Creates the routes for the API server.
// Example: localhost:8000/api will show "testAPIPoint"
// in the HTML page.
// Docs Routes
router
.get("/", (ctx: any) => {
ctx.response.body = "For endpoints, use /api/{name}";
})
.get("/api", (ctx: any) => {
ctx.response.body = "For API Documentation, visit /docs";
});
// -- Account routes --
router
.post("/api/account/login", api_login)
.post("/api/account/register", api_register)
.post("/api/account/logout", () => {}) // TODO
.post("/api/account/password/forgot", () => {}) // TODO
.post("/api/account/password/reset", () => {}) // TODO
.post("/api/account/password/change", () => {}) // TODO
.post("/api/account/email/change-email", () => {}) // TODO
.post("/api/account/email/verify-email", () => {}) // TODO
.post("/api/account/delete-account", () => {}) // TODO
.post("/api/account/block", () => {}); // TODO
// -- Auth Routes -- //
router
.get("/api/auth", () => {}) // TODO
.get("/api/auth/verify", () => {}) // TODO
.get("/api/auth/refresh", () => {}); // TODO
// -- User routes -- //
router.get("/api/users", api_getAllUsers);
// .get("/api/user/:id/info", api_user_getInfo);
// -- Chat routes -- //
router
.get("/api/chats", api_getChats)
.get("/api/chat/:id", api_getChatById)
.post("/api/chat/create", api_createChat)
.post("/api/chat/:id/message", api_sendMessage)
.delete("/api/chat/:id", api_deleteChat);
// -- Post routes -- //
router
.get("/api/posts", api_posts_getAll)
.get("/api/post/:id", api_getPostById)
.post("/api/post/create", api_createPost)
.put("/api/post/:id", api_updatePost)
.delete("/api/post/:id", api_deletePost)
.post("/api/post/:id/like", api_likePost);
// -- Comment Routes -- //
router
.get("/api/post/:id/comments", api_getPostComments)
.post("/api/post/:id/comment", api_createComment)
.put("/api/comment/:id", api_updateComment)
.delete("/api/comment/:id", api_deleteComment)
.post("/api/comment/:id/like", api_likeComment);
// +++ FUNCTIONS ----------------------------------------------------- //
// ABANDONED FUNCTIONS //
async function _authenticator(ctx: Context, next: Next): Promise<void> {
const authHeader = ctx.request.headers.get("Authorization");
if (!authHeader) {
ctx.response.status = 401;
ctx.response.body = { error: "No header" };
return;
}
// Bearer check
// Bearer is often used for authentication in API's and is a standard, I check it using RegEx (Regular Expressions)
const match = authHeader.match(/^Bearer (.+)$/);
if (!match) {
ctx.response.status = 401;
ctx.response.body = { error: "Invalid format" };
return;
}
const token = match[1];
try {
// Token logic missing, unattempted.
await next();
} catch (error) {
ctx.response.status = 401;
ctx.response.body = { error: "Invalid token" };
}
}
async function _tokenChecker(ctx: Context, next: Next): Promise<void> {
// logic below (TODO):
/**
* 1. check if the token is valid
* 2. if valid, set the user in the context
* 3. if not valid, return 401
* 4. if: token missing, expired, blacklisted, !DB, !user or !correct user, return 401 with associated error
* eg: wrong user: 401 -> "Token not associated with this user"
*/
}
// API: Posts //
async function api_posts_getAll(ctx: Context): Promise<void> {
const posts = await db_utils.getPostsFromDB();
ctx.response.body = posts;
}
// API: login/register //
async function api_register(ctx: Context): Promise<void> {
try {
const body = ctx.request.body;
const result = await body.json();
const {
username,
password,
userGroup,
displayname,
user_email,
firstname,
surname,
} = result;
// Claude 3-5 Sonnet was used for the first Date formatting
const account_created = `${Math.floor(Date.now() / 1000)}-${
new Date()
.toLocaleDateString("en-GB")
.split("/")
.join("-")
}`;
if (
!username ||
!password ||
!userGroup ||
!displayname ||
!user_email ||
!firstname ||
!surname
) {
helper_utils.errorResponse(ctx, 400, "Missing required fields");
return;
}
// First salt the password
const { saltedPassword, salt } = await helper_utils.saltPassword(password);
// Then hash the salted password
const hash = await helper_utils.hashPassword(saltedPassword);
const userId = db_utils.registerUser(
username,
hash,
salt,
userGroup,
displayname,
user_email,
firstname,
surname,
account_created,
);
const user = await db_utils.getUserByUsername(username);
const responseBody: any = {
success: true,
message: "Register successful",
};
if (user.user_id !== undefined) {
responseBody.userId = user.user_id;
}
helper_utils.sendResponse(ctx, {
status: 200,
body: responseBody,
});
} catch (error) {
console.log(error);
helper_utils.errorResponse(ctx, 500, "Invalid request");
return;
}
}
async function api_login(ctx: Context): Promise<string> {
try {
const body = ctx.request.body;
const result = await body.json();
const { username, password } = result;
if (!username || !password) {
helper_utils.errorResponse(ctx, 400, "Missing required fields");
return "Error";
}
const user = await db_utils.getUserByUsername(username);
if (!user) {
helper_utils.errorResponse(ctx, 404, "User not found");
return "Error";
}
const storedSalt = user.password_salt;
const saltedPassword = `${password}${storedSalt}`;
const hash = await helper_utils.hashPassword(saltedPassword);
// Compare the hashed password with the stored hash
if (user.password !== hash) {
helper_utils.errorResponse(ctx, 401, "Invalid password");
return "Error";
}
// Return success with the user ID if it exists
const responseBody: any = {
success: true,
message: "Login successful",
};
// Only add userId if it exists
if (user.user_id !== undefined) {
responseBody.userId = user.user_id;
}
helper_utils.sendResponse(ctx, {
status: 200,
body: responseBody,
});
return "Success";
} catch (error) {
console.log(error);
helper_utils.errorResponse(ctx, 500, "Invalid request");
return "Error";
}
}
// +++ APP ---------------------------------------------------------- //
app.use(
oakCors({
origin: "*",
credentials: true,
}),
);
app.use(router.routes());
app.use(router.allowedMethods());
export { app };
await app.listen({ port: 8000 });