Compare commits

39 Commits

Author SHA1 Message Date
danielvici123
94c14226dc Only minor adjustments 2025-04-09 09:31:20 +02:00
danielvici123
0f9284d515 Merge branch 'main' of https://github.com/danielvici/esp-projekt 2025-04-06 21:07:59 +02:00
danielvici123
4be4a26036 suche geht jetzt, special sachen für user 99 (danielvici gemacht) und in einstellungen werden daten angezeigt 2025-04-06 21:07:57 +02:00
deno-deploy[bot]
ed70ace33d [Deno Deploy] Update .github/workflows/deploy.yml 2025-04-04 06:49:22 +00:00
deno-deploy[bot]
dc29170a58 [Deno Deploy] Update .github/workflows/deploy.yml 2025-04-04 06:34:15 +00:00
deno-deploy[bot]
c2f56b2b5d [Deno Deploy] Update .github/workflows/deploy.yml 2025-04-04 06:25:06 +00:00
deno-deploy[bot]
86bc0347ca [Deno Deploy] Update .github/workflows/deploy.yml 2025-04-04 06:05:15 +00:00
deno-deploy[bot]
21c77cd34a [Deno Deploy] Add .github/workflows/deploy.yml 2025-04-04 06:04:01 +00:00
Lynixenn
8f27246234 Fixed Database creation, The error was that there were 2 simultaenous Database connections, causing an error when both attempted to connect at once. 2025-03-30 22:59:19 +02:00
Lynixenn
fb916e9e9f Merged API and Databank into Main branch 2025-03-30 22:02:56 +02:00
Esad Mustafoski
3cd92d7fff Update deno.json 2025-03-29 00:36:27 +01:00
danielvici123
915e91fbd1 +responsiv, +profile site, 2025-03-28 23:06:37 +01:00
danielvici123
9a8237de42 etwas responsible gemacht, posten und commentieren geht profile machen 2025-03-26 22:43:39 +01:00
danielvici123
5a5cf84197 ui responsive gemacht 2025-03-26 15:51:17 +01:00
danielvici123
7832267081 testphase posten 2025-03-25 22:23:57 +01:00
danielvici123
96f9071edd liken von posts geht (halb)
im feed gehs aber sonst nicht weil nach dem aktualisieren  geht es weg
2025-03-24 23:05:28 +01:00
danielvici123
f1b8dfe92f comments in posts sieht mann jetzt, link zum post copyn geht und kleine änderungen 2025-03-24 20:28:47 +01:00
danielvici123
834149ffb9 posts ansehen, posts im detial, scrollbar entfernt
scrollbar:  - nurnoch in bestimmten elementen
2025-03-23 21:20:43 +01:00
danielvici123
c2d5cc6a73 Delete api directory
verursacht eventuell probleme
2025-03-22 16:11:49 +01:00
danielvici123
2d29b9a3a4 Delete database directory
verursacht eventuelle probleme
2025-03-22 16:11:22 +01:00
danielvici123
a5fa8d840c Update profile_main.vue
problem gefixt das massive probleme verruscacht hat
2025-03-22 16:10:45 +01:00
danielvici123
932edc5a2b kleine änderung/anpassungen, login zwang, messages hinzugefügt, 2025-03-22 12:51:56 +01:00
danielvici123
7331b1b9b0 link zu icons8 hizugefügt 2025-03-12 15:24:13 +01:00
danielvici123
c7365703df quick chat entfernt, profil seiten hinzugefügt, chatt (contacts) verändert 2025-03-12 14:11:25 +01:00
danielvici123
4411c7def6 Navigationsbar angepasst und post view hinzugefügt (geht nicht wegen keine datenbank aanschluss) 2025-02-18 18:33:40 +01:00
danielvici123
eca851dc08 kleine veränderung und popup (bei kontakte) sowie testphase stay logged in 2025-02-18 17:48:10 +01:00
danielvici123
9af5b34658 suche geht jetzt 2025-02-06 17:46:18 +01:00
danielvici123
bb2cfedb84 suche (angefangen) und kleine änderungen
kleine veränderungen
suche anfangen -> filter und suche ansich geht nicht
2025-02-05 20:35:29 +01:00
danielvici123
8b4b99a015 Register gemacht. Kleine Änderung
Kleine Änderung:
- In Login:
    - Logo RICHTIG gefärbt
    - Remember me (keine Funktion) hinzufegüt
    - Weiterleitung auf register
Register:
Registrierung seite erstellt und registrieren möglich gemacht + API anbindung
2024-12-22 20:08:47 +01:00
danielvici123
1d8665d6ce small changes+ dokcer file added (NOT WORKING)
small changes
dockerfile added but its not working
2024-12-21 11:12:43 +01:00
danielvici123
aa690f0989 Merge branch 'main' of https://github.com/danielvici/esp-projekt 2024-12-18 21:29:14 +01:00
danielvici123
3683be8202 login updated
Mann kann jetzt text in den input rein schreiben und das wird dann an di api geschickt. (mit bugs)
2024-12-18 21:29:11 +01:00
danielvici123
d0182e7d5b small changes, login first version
small changes:
- Title changed -> ESP - Express, Share, Post
- Icon changed -> Logo
login:
connection to api :)
2024-12-17 22:24:23 +01:00
danielvici123
5bf7d62f71 Update README.md 2024-12-14 20:04:59 +01:00
danielvici123
f478198ff7 Merge branch 'main' of https://github.com/danielvici/esp-projekt 2024-12-14 19:56:11 +01:00
danielvici123
dc79759d38 Notifications gemacht. Settings, Login verändert
Nachrichten:
- Nachrichten anzeigen mit filter usw.
Login:
- Password ist sind jetzt punkte
Settings, WIP:
- Kleine Stylische anpassungen
2024-12-14 19:55:10 +01:00
danielvici123
17a23a992e kleine veränderungen
navbar
wip site
2024-11-14 10:12:58 +01:00
danielvici123
2352c25c05 Settings added (only one part), notification started and highlight added 2024-11-10 18:10:28 +01:00
danielvici123
860cd027cd some comments added 2024-11-05 11:14:24 +01:00
75 changed files with 5272 additions and 682 deletions

41
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Deploy
on:
push:
branches: main
pull_request:
branches: main
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
permissions:
id-token: write # Needed for auth with Deno Deploy
contents: read # Needed to clone the repository
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Install Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install step
run: "npm install"
- name: Upload to Deno Deploy
uses: denoland/deployctl@v1
with:
project: "esp-projekt"
entrypoint: "src/main.ts"
root: ""

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ dist
dist-ssr dist-ssr
.vite .vite
*.local *.local
*.sqlite
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

View File

@@ -10,19 +10,22 @@ ESP is a simple social media platform built with [Deno](https://deno.land/), [Vi
We're building ESP to avoid the clutter of other Social Media websites. We're building ESP to avoid the clutter of other Social Media websites.
It's build to be simple, fast and easy to use, all while keeping a Minimal feeling to it. It's built to be simple, fast and easy to use, all while keeping a Minimal feeling to it.
--- ---
# How do I run it? # How do I run it?
The only dependency is Deno! Nothing else is required, Deno will install all the Modules needed and start it.
## Running ## Running
Dev server: API + Website:
```bash ```bash
$ deno task dev $ deno task dev
``` ```
API only: API only:
```bash ```bash
$ deno task dev:api $ deno task dev:api
@@ -52,19 +55,16 @@ $ deno task build
<li><a href="https://vitejs.dev/"><img src="https://vitejs.dev/logo.svg" width="20" height="20"> Vite</a></li> <li><a href="https://vitejs.dev/"><img src="https://vitejs.dev/logo.svg" width="20" height="20"> Vite</a></li>
<li><a href="https://www.typescriptlang.org/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Typescript_logo_2020.svg/512px-Typescript_logo_2020.svg.png?20221110153201" width="20" height="20"> TypeScript</a></li> <li><a href="https://www.typescriptlang.org/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Typescript_logo_2020.svg/512px-Typescript_logo_2020.svg.png?20221110153201" width="20" height="20"> TypeScript</a></li>
<li><a href="https://oakserver.github.io/oak/"><img src="https://oakserver.org/oak_logo.svg?__frsh_c=2e345d91800d1e0a52aa35efcaab769fa1768888" width="20" height="20"> Oak</a></li> <li><a href="https://oakserver.github.io/oak/"><img src="https://oakserver.org/oak_logo.svg?__frsh_c=2e345d91800d1e0a52aa35efcaab769fa1768888" width="20" height="20"> Oak</a></li>
<li><a href="https://deno.land/x/denodb"><img src="https://github.com/eveningkid/denodb/blob/master/design/logo.png?raw=true" width="20" height="20"> DenoDB</a></li> <li><a href="https://www.sqlite.org/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/SQLite370.svg/220px-SQLite370.svg.png" width="20" height="20"> SQLite</a></li>
<li><a href="https://tailwindcss.com/"><img src="https://tailwindcss.com/_next/static/media/tailwindcss-mark.3c5441fc7a190fb1800d4a5c7f07ba4b1345a9c8.svg" width="20" height="20"> TailwindCSS</a></li> <li><a href="https://tailwindcss.com/"><img src="https://tailwindcss.com/_next/static/media/tailwindcss-mark.3c5441fc7a190fb1800d4a5c7f07ba4b1345a9c8.svg" width="20" height="20"> TailwindCSS</a></li>
<li><a href="https://vuejs.org/"><img src="https://upload.wikimedia.org/wikipedia/commons/9/95/Vue.js_Logo_2.svg" width="20" height="20">VueJs</a></li> <li><a href="https://vuejs.org/"><img src="https://upload.wikimedia.org/wikipedia/commons/9/95/Vue.js_Logo_2.svg" width="20" height="20">Vue</a></li>
</ul> </ul>
## Tools used ## Tools used
<ul> <ul>
<li><a href="https://www.jetbrains.com/webstorm/"><img src="https://upload.wikimedia.org/wikipedia/commons/c/c0/WebStorm_Icon.svg" width="20" height="20"> JetBrains WebStorm</a></li> <li><a href="https://www.jetbrains.com/webstorm/"><img src="https://upload.wikimedia.org/wikipedia/commons/c/c0/WebStorm_Icon.svg" width="20" height="20"> JetBrains WebStorm</a></li>
<li><a href="https://www.jetbrains.com/datagrip/"><img src="https://seeklogo.com/images/D/datagrip-logo-295CA63255-seeklogo.com.png" width="20" height="20"> JetBrains DataGrip</a></li> <li><a href="https://www.jetbrains.com/datagrip/"><img src="https://seeklogo.com/images/D/datagrip-logo-295CA63255-seeklogo.com.png" width="20" height="20"> JetBrains DataGrip</a></li>
<li><a href="https://neovim.io/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Neovim-mark-flat.svg/640px-Neovim-mark-flat.svg.png" width="20" height="20"> Neovim</a></li> <li><a href="https://neovim.io/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Neovim-mark-flat.svg/640px-Neovim-mark-flat.svg.png" width="20" height="20"> Neovim</a></li>
<li><a href="https://code.visualstudio.com/"><img src="https://code.visualstudio.com/assets/images/code-stable.png" width="20" height="20"> Visual Studio Code</a></li>
</ul> </ul>

View File

@@ -1,6 +0,0 @@
INSERT INTO posts (posts_uuid, user_id, created_at, post_text, likes, comments) VALUES
('1a2b3c4d', 1, '2024-11-01 10:00:00', 'Post1', 5, 2),
('2b3c4d5e', 1, '2024-11-02 11:30:00', 'Post2', 10, 3),
('3c4d5e6f', 2, '2024-11-03 12:45:00', 'Post3', 0, 0),
('4d5e6f7g', 2, '2024-11-04 14:20:00', 'Post4', 15, 5),
('5e6f7g8h', 3, '2024-11-05 09:15:00', 'Post5', 8, 1);

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
};

View File

@@ -1,44 +1,307 @@
// main API file. Handles all the routing/api stuff /// <reference lib="deno.ns" />
/**
// Due to the Language servers, the import statements are * @author Esad Mustafoski
// shown as errors, @ts-ignore is used to ignore them. * @description Main API file, Handles all the routing/api stuff
// This is a Deno file, but the Vue LSP is still */
// attempting to find errors, which causes
// confusing False errors
// +++ IMPORTS ------------------------------------------------------ // // +++ IMPORTS ------------------------------------------------------ //
import { Application, Router } from "https://deno.land/x/oak/mod.ts"; import {
import { oakCors } from "https://deno.land/x/cors/mod.ts"; Application,
import * as db_utils from "../database/utils.ts"; 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";
// +++ VARIABLES ---------------------------------------------------- // 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 router = new Router();
const app = new Application(); 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.
await db_utils.ensureDatabaseExists();
// +++ ROUTER ------------------------------------------------------- // // +++ ROUTER ------------------------------------------------------- //
// Creates the routes for the API server. // Creates the routes for the API server.
// Example: localhost:8000/api will show "testAPIPoint" // Example: localhost:8000/api will show "testAPIPoint"
// in the HTML // in the HTML page.
// Docs Routes
router router
.get("/", (ctx) => { .get("/", (ctx: any) => {
ctx.response.body = "ESP API Site"; ctx.response.body = "For endpoints, use /api/{name}";
}) })
.get("/api", (ctx) => { .get("/api", (ctx: any) => {
ctx.response.body = "testAPIPoint"; ctx.response.body = "For API Documentation, visit /docs";
}) });
.get("/api/users", (ctx) => {
ctx.response.body = "Info from all users here"; //getAllUsers(); // -- Account routes --
}) router
.get("/api/posts", async (ctx) => { .post("/api/account/login", api_login)
const getPosts = await db_utils.getPostsFromDB(); .post("/api/account/register", api_register)
const countedPosts = await db_utils.countPosts(); .post("/api/account/logout", () => {}) // TODO
ctx.response.body = { getPosts, countedPosts }; .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,
}); });
app.use(oakCors()); 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.routes());
app.use(router.allowedMethods()); app.use(router.allowedMethods());
// @ts-ignore: start app export { app };
await app.listen({ port: 8000 }); await app.listen({ port: 8000 });

104
database/create_db.ts Normal file
View File

@@ -0,0 +1,104 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description This file is responsible for creating the database and the tables
*/
// +++ IMPORTS ------------------------------------------------------ //
import { DB } from "https://deno.land/x/sqlite@v3.9.1/mod.ts";
// +++ VARIABLES ---------------------------------------------------- //
export function createDatabase(db: DB): void {
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,
password_salt 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,
post_type TEXT,
likes INTEGER,
comments INTEGER
);
CREATE TABLE IF NOT EXISTS comments (
comment_id INTEGER PRIMARY KEY AUTOINCREMENT,
post_id INTEGER,
author_user_id INTEGER,
date_created_at TEXT,
text TEXT,
likes INTEGER
);
CREATE TABLE IF NOT EXISTS messages (
message_id INTEGER PRIMARY KEY AUTOINCREMENT,
chat_id INTEGER,
sender_id INTEGER,
content TEXT,
timestamp TEXT,
FOREIGN KEY (chat_id) REFERENCES chats (chat_id),
FOREIGN KEY (sender_id) REFERENCES accounts (user_id)
);
CREATE TABLE IF NOT EXISTS chats (
chat_id INTEGER PRIMARY KEY AUTOINCREMENT,
chat_name TEXT,
participants TEXT,
created_at TEXT
);
CREATE TABLE IF NOT EXISTS marker (
id INTEGER PRIMARY KEY AUTOINCREMENT
);
`);
}
// Sample data generated using AI, does not work yet and will be adjusted
export function insertSampleData(db: DB): void {
db.query(
`INSERT INTO accounts (user_group, bio, displayname, username, user_email, password, password_salt, firstname, surname, account_created, blocked_users, followers, following, contacts) VALUES
('admin', 'Admin bio', 'Admin User', 'admin', 'admin@example.com', 'pw1', 'salt1', 'Admin', 'User', '2024-01-01', '[]', '[]', '[]', '[]'),
('user', 'I love coding!', 'John Dev', 'johndev', 'john@example.com', 'pw2', 'salt2', 'John', 'Smith', '2024-01-02', '[]', '[]', '[3,4]', '[1,2]'),
('user', 'Photography enthusiast', 'Alice', 'alice_photo', 'alice@example.com', 'pw3', 'salt3', 'Alice', 'Johnson', '2024-01-03', '[]', '[5]', '[1]', '[2]')
`,
);
db.query(
`INSERT INTO posts (user_id, created_at, post_text, post_type, likes, comments) VALUES
(1, '2024-01-15 10:00:00', 'First post about programming!', 'text', 5, 2),
(1, '2024-01-15 11:30:00', 'Check out this new feature', 'text', 10, 3),
(2, '2024-01-16 09:15:00', 'Just learned about TypeScript', 'text', 8, 1),
(3, '2024-01-16 14:20:00', 'Posted my new photo collection', 'image', 15, 4)
`,
);
db.query(
`INSERT INTO comments (post_id, author_user_id, date_created_at, text, likes) VALUES
(1, 2, '2024-01-15 10:05:00', 'Great post!', 3),
(1, 3, '2024-01-15 10:10:00', 'Very informative', 2),
(2, 3, '2024-01-15 11:35:00', 'Nice feature', 4),
(3, 1, '2024-01-16 09:20:00', 'TypeScript is awesome', 5),
(4, 2, '2024-01-16 14:25:00', 'Beautiful photos!', 6)
`,
);
}

View File

Binary file not shown.

View File

@@ -0,0 +1,61 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description This file is responsible for making Interfaces accessible by every file, deduplicating it.
* @file interfaces.ts
*/
interface Post {
posts_uuid: number;
user_id: number;
created_at: string;
post_text: string;
post_type: string;
likes: number;
comments: number;
}
interface Accounts {
user_id: number;
user_group: string;
bio: string;
displayname: string;
username: string;
user_email: string;
password: string;
password_salt: string;
firstname: string;
surname: string;
account_created: string;
blocked_users: string;
followers: string;
following: string;
contacts: string;
}
interface Comments {
comment_id: number;
post_id: number;
author_user_id: number;
date_created_at: string;
text: string;
likes: number;
}
interface Chat {
chat_id: number;
chat_name: string;
participants: string;
created_at: string;
}
interface Message {
message_id: number;
chat_id: number;
sender_id: number;
content: string;
timestamp: string;
}
export type { Accounts, Chat, Comments, Message, Post };

View File

@@ -0,0 +1,122 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description This file makes accessing the Database easier by creating a map for each type of data
* @file maphelper.ts
*/
// +++ IMPORTS ------------------------------------------------------ //
import { Row } from "https://deno.land/x/sqlite@v3.9.1/mod.ts";
import {
Accounts,
Chat,
Comments,
Message,
Post,
} from "../helpers/interfaces.ts";
function mapPostRow(row: Row): Post {
const [
posts_uuid,
user_id,
created_at,
post_text,
post_type,
likes,
comments,
] = row;
return {
posts_uuid: Number(posts_uuid),
user_id: Number(user_id),
created_at: String(created_at),
post_text: String(post_text),
post_type: String(post_type),
likes: Number(likes),
comments: Number(comments),
};
}
function mapCommentRow(row: Row): Comments {
const [
comment_id,
post_id,
author_user_id,
date_created_at,
text,
likes,
] = row;
return {
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),
};
}
function mapChatRow(row: Row): Chat {
const [chat_id, chat_name, participants, created_at] = row;
return {
chat_id: Number(chat_id),
chat_name: String(chat_name),
participants: String(participants),
created_at: String(created_at),
};
}
function mapMessageRow(row: Row): Message {
const [message_id, chat_id, sender_id, content, timestamp] = row;
return {
message_id: Number(message_id),
chat_id: Number(chat_id),
sender_id: Number(sender_id),
content: String(content),
timestamp: String(timestamp),
};
}
function mapAccountRow(row: Row): Accounts {
const [
user_id,
user_group,
bio,
displayname,
username,
user_email,
password,
password_salt,
firstname,
surname,
account_created,
blocked_users,
followers,
following,
contacts,
] = row;
return {
user_id: Number(user_id),
user_group: String(user_group),
bio: String(bio),
displayname: String(displayname),
username: String(username),
user_email: String(user_email),
password: String(password),
password_salt: String(password_salt),
firstname: String(firstname),
surname: String(surname),
account_created: String(account_created),
blocked_users: String(blocked_users),
followers: String(followers),
following: String(following),
contacts: String(contacts),
};
}
export {
mapAccountRow,
mapChatRow,
mapCommentRow,
mapMessageRow,
mapPostRow
};

View File

@@ -0,0 +1,86 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description This file is responsible for creating Functions to easily access the Database, Specifically for Chats
* @file chatUtil.ts
*/
// +++ IMPORTS ------------------------------------------------------ //
import { DB } from "https://deno.land/x/sqlite@v3.9.1/mod.ts";
import { Chat, Message } from "../interfaces.ts";
import { mapChatRow, mapMessageRow, queryDatabase } from "./mod.ts";
async function getUserChats(db: DB, userId: string): Promise<Chat[]> {
const query = `SELECT * FROM chats WHERE participants LIKE '%${userId}%'`;
return await queryDatabase<Chat>(db, query, [], mapChatRow);
}
async function getChatById(db: DB, chatId: string): Promise<Chat | null> {
const query = `SELECT * FROM chats WHERE chat_id = ?`;
const chats = await queryDatabase<Chat>(db, query, [chatId], mapChatRow);
return chats.length > 0 ? chats[0] : null;
}
async function getChatMessages(db: DB, chatId: string): Promise<Message[]> {
const query =
`SELECT * FROM messages WHERE chat_id = ? ORDER BY timestamp ASC`;
return await queryDatabase<Message>(db, query, [chatId], mapMessageRow);
}
async function createChat(
db: DB,
participants: string[],
chatName: string,
): Promise<string> {
const participantsJson = JSON.stringify(participants);
const timestamp = `${Math.floor(Date.now() / 1000)}-${
new Date().toLocaleDateString("en-GB").split("/").join("-")
}`;
const query = `
INSERT INTO chats (chat_name, participants, created_at)
VALUES (?, ?, ?)
`;
db.query(query, [chatName, participantsJson, timestamp]);
return db.lastInsertRowId.toString();
}
async function addMessageToChat(
db: DB,
chatId: string,
senderId: string,
content: string,
): Promise<string> {
const timestamp = `${Math.floor(Date.now() / 1000)}-${
new Date().toLocaleDateString("en-GB").split("/").join("-")
}`;
const query = `
INSERT INTO messages (chat_id, sender_id, content, timestamp)
VALUES (?, ?, ?, ?)
`;
db.query(query, [chatId, senderId, content, timestamp]);
return db.lastInsertRowId.toString();
}
async function deleteChat(db: DB, chatId: string): Promise<void> {
// First delete all messages in the chat
const deleteMessagesQuery = `DELETE FROM messages WHERE chat_id = ?`;
db.query(deleteMessagesQuery, [chatId]);
// Then delete the chat itself
const deleteChatQuery = `DELETE FROM chats WHERE chat_id = ?`;
db.query(deleteChatQuery, [chatId]);
}
export {
getUserChats,
getChatById,
getChatMessages,
createChat,
addMessageToChat,
deleteChat
};

View File

@@ -0,0 +1,81 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description This file is responsible for creating Functions to easily access the Database, Specifically for Comments
* @file commentUtil.ts
*/
import { DB } from "https://deno.land/x/sqlite@v3.9.1/mod.ts";
import { Comments } from "../interfaces.ts";
import { mapCommentRow, queryDatabase } from "./mod.ts";
async function getCommentsFromDB(
db: DB,
post_id?: number,
): Promise<Comments[]> {
const query = post_id
? `SELECT * FROM comments WHERE post_id = ?`
: `SELECT * FROM comments`;
const params = post_id ? [post_id] : [];
return await queryDatabase<Comments>(db, query, params, mapCommentRow);
}
function createComment(
db: DB,
postId: string,
userId: string,
createdAt: string,
text: string,
): string {
const query = `
INSERT INTO comments (post_id, author_user_id, date_created_at, text, likes)
VALUES (?, ?, ?, ?, 0)
`;
db.query(query, [postId, userId, createdAt, text]);
const updatePostQuery =
`UPDATE posts SET comments = comments + 1 WHERE posts_uuid = ?`;
db.query(updatePostQuery, [postId]);
return db.lastInsertRowId.toString();
}
async function updateComment(
db: DB,
commentId: string,
text: string,
): Promise<void> {
const query = `UPDATE comments SET text = ? WHERE comment_id = ?`;
db.query(query, [text, commentId]);
}
async function deleteComment(db: DB, commentId: string): Promise<void> {
const getPostIdQuery = `SELECT post_id FROM comments WHERE comment_id = ?`;
const result = db.query(getPostIdQuery, [commentId]);
const postId: any = result[0][0];
const deleteCommentQuery = `DELETE FROM comments WHERE comment_id = ?`;
db.query(deleteCommentQuery, [commentId]);
const updatePostQuery =
`UPDATE posts SET comments = comments - 1 WHERE posts_uuid = ?`;
db.query(updatePostQuery, [postId]);
}
async function likeComment(
db: DB,
commentId: string,
userId: string,
): Promise<void> {
const query = `UPDATE comments SET likes = likes + 1 WHERE comment_id = ?`;
db.query(query, [commentId]);
}
export {
createComment,
deleteComment,
getCommentsFromDB,
likeComment,
updateComment,
};

View File

@@ -0,0 +1,46 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description Core Utility for querying the Database and inserting Sample Data
* @file core_utils.ts
*/
// +++ IMPORTS ------------------------------------------------------ //
import { DB, Row } from "https://deno.land/x/sqlite@v3.9.1/mod.ts";
import * as db_create from "../../create_db.ts";
// +++ Helper ------------------------------------------------------ //
// "T" is a generic type, it can be anything and makes the function "flexible"(?)
async function queryDatabase<T>(
db: DB,
query: string,
params: any[],
mapRow: (row: Row) => T,
): Promise<T[]> {
const results: T[] = [];
try {
const rows = await db.query(query, params);
for (const row of rows) {
results.push(mapRow(row));
}
} catch (error) {
console.error("Database query error:", error);
}
return results;
}
// +++ FUNCTIONS --------------------------------------------------- //
/**
* See:
* @file ./create_db.ts
*/
function insertSamples(): void {
db_create.insertSampleData();
}
function createDatabaseIfNotExist(): void {
db_create.createDatabase();
}
export { queryDatabase, insertSamples, createDatabaseIfNotExist };

View File

@@ -0,0 +1,13 @@
/// <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_utils.ts";
export * from "./comment_utils.ts";
export * from "./post_utils.ts";
export * from "./user_utils.ts";
export * from "./core_utils.ts"
export * from "../maphelper.ts";

View File

@@ -0,0 +1,128 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description This file is responsible for creating Functions to easily access the Database, Specifically for Posts
* @file postUtil.ts
*/
// +++ IMPORTS ------------------------------------------------------ //
import { DB } from "https://deno.land/x/sqlite@v3.9.1/mod.ts";
import { Post } from "../interfaces.ts";
import { mapPostRow, queryDatabase } from "./mod.ts";
async function getPostsFromDB(db: DB, user_uuid?: string): Promise<Post[]> {
const query = user_uuid
? `SELECT * FROM posts WHERE user_id = ?`
: `SELECT * FROM posts`;
const params = user_uuid ? [user_uuid] : [];
return await queryDatabase<Post>(db, query, params, mapPostRow);
}
async function getPostById(db: DB, postId: string): Promise<Post | null> {
const query = `SELECT * FROM posts WHERE posts_uuid = ?`;
const posts = await queryDatabase<Post>(db, query, [postId], mapPostRow);
return posts.length > 0 ? posts[0] : null;
}
async function createPost(
db: DB,
userId: string,
createdAt: string,
postText: string,
postType: string,
): Promise<string> {
const query = `
INSERT INTO posts (user_id, created_at, post_text, post_type, likes, comments)
VALUES (?, ?, ?, ?, 0, 0)
`;
db.query(query, [userId, createdAt, postText, postType]);
return db.lastInsertRowId.toString();
}
async function updatePost(
db: DB,
postId: string,
postText?: string,
postType?: string,
): Promise<void> {
let query = `UPDATE posts SET `;
const params: any[] = [];
if (postText) {
query += `post_text = ?`;
params.push(postText);
if (postType) {
query += `, post_type = ?`;
params.push(postType);
}
} else if (postType) {
query += `post_type = ?`;
params.push(postType);
}
query += ` WHERE posts_uuid = ?`;
params.push(postId);
db.query(query, params);
}
// This function deletes the comments on the post first, then
// deletes the post to avoid errors
async function deletePost(db: DB, postId: string): Promise<void> {
const deleteCommentsQuery = `DELETE FROM comments WHERE post_id = ?`;
db.query(deleteCommentsQuery, [postId]);
const deletePostQuery = `DELETE FROM posts WHERE posts_uuid = ?`;
db.query(deletePostQuery, [postId]);
}
// This is simplified and doesn't work as it would in a real application
// or website like twitter, this only exists as a test
async function likePost(db: DB, postId: string, userId: string): Promise<void> {
const query = `UPDATE posts SET likes = likes + 1 WHERE posts_uuid = ?`;
db.query(query, [postId]);
}
/**
* @param posts_to_filter The Posts in an array to filter
* @param post_types The types of Posts to filter for
* @returns Array of Posts
*/
// Filter functions merged to one
function filterPosts(posts_to_filter: Post[], post_types: string[]): Post[] {
if (post_types.length === 0) {
return posts_to_filter;
}
// Set is a type that we can specify the values to
// example: const set = new Set<number>();
// set.add(1); Will work
// set.add("2"); Will not work, it will error because
// it is a string.
const includedPostIds = new Set<number>();
const result: Post[] = [];
for (const type of post_types) {
for (const post of posts_to_filter) {
if (!includedPostIds.has(post.posts_uuid) && post.post_type === type) {
result.push(post);
includedPostIds.add(post.posts_uuid);
}
}
}
return result;
}
export {
getPostsFromDB,
getPostById,
createPost,
updatePost,
deletePost,
likePost,
filterPosts,
};

View File

@@ -0,0 +1,103 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* @description This file is responsible for creating Functions to easily access the Database, Specifically for Users
* @file userUtil.ts
*/
// +++ IMPORTS ------------------------------------------------------ //
import { DB, Row } from "https://deno.land/x/sqlite@v3.9.1/mod.ts";
import { mapAccountRow, queryDatabase } from "./mod.ts";
import { Accounts } from "../interfaces.ts";
/**
* @param user The username of the User to add
* @param password The hashed password of the User to add
* @param salt The salt used for the password
* @returns "noUser" if user exists, "newUser" if registration successful
*/
function registerUser(
db: DB,
user: string,
password: string,
salt: string,
userGroup: string,
displayname: string,
user_email: string,
firstname: string,
surname: string,
account_created: string,
): any {
const query_user_exists =
`SELECT * FROM accounts WHERE displayname = '${user}'`;
if (!query_user_exists) {
return "noUser";
}
const query_add_user = `
INSERT INTO accounts (
username,
password,
password_salt,
user_group,
displayname,
user_email,
firstname,
surname,
account_created,
bio,
blocked_users,
followers,
following,
contacts
) VALUES (
'${user}',
'${password}',
'${salt}',
'${userGroup}',
'${displayname}',
'${user_email}',
'${firstname}',
'${surname}',
'${account_created}',
'',
'[]',
'[]',
'[]',
'[]'
)`;
db.query(query_add_user);
const userId = db.query(
`SELECT user_id FROM accounts WHERE displayname = '${user}'`,
);
console.log(`New user: ${user}`);
return userId;
}
/**
* @returns Array of all Users in the Database
*/
async function getAllUsersFromDB(db: DB): Promise<Accounts[]> {
const query = `SELECT * FROM accounts`;
return await queryDatabase<Accounts>(db, query, [], mapAccountRow);
}
/**
* @param username
* @returns Returns the Accounts for the User with the given username
*/
async function getUserByUsername(db: DB, username: string): Promise<Accounts> {
const query = `SELECT * FROM accounts WHERE username = '${username}'`;
const params: string[] = [];
const result = await queryDatabase<Accounts>(
db,
query,
params,
mapAccountRow,
);
return result[0];
}
export { getAllUsersFromDB, getUserByUsername, registerUser };

BIN
database/sqlite3.exe Normal file

Binary file not shown.

View File

@@ -1,138 +1,208 @@
import { DB } from "https://deno.land/x/sqlite/mod.ts"; /// <reference lib="deno.ns" />
import { dirname, fromFileUrl, join } from "https://deno.land/std/path/mod.ts";
/**
* @author Esad Mustafoski
* @description This file is responsible for creating Functions to easily access the Database, Intended for use in the API
*/
// __dirname Is never getting used again, It's only needed because the DB Import // +++ IMPORTS ------------------------------------------------------ //
import { DB } from "https://deno.land/x/sqlite@v3.9.1/mod.ts";
import {
dirname,
fromFileUrl,
join,
} from "https://deno.land/std@0.224.0/path/mod.ts";
import * as db_create from "./create_db.ts";
// Import all internal utilities with renamed imports to avoid naming conflicts
import {
addMessageToChat as addMessageToChatInternal,
createChat as createChatInternal,
// getCommentsForComments as getCommentsForCommentsInternal,
// Accidentally deleted function...
createComment as createCommentInternal,
createPost as createPostInternal,
deleteChat as deleteChatInternal,
deleteComment as deleteCommentInternal,
deletePost as deletePostInternal,
filterPosts,
// --- Account Functions --- //
getAllUsersFromDB as getAllUsersFromDBInternal,
getChatById as getChatByIdInternal,
getChatMessages as getChatMessagesInternal,
// --- Comment Functions --- //
getCommentsFromDB as getCommentsFromDBInternal,
getPostById as getPostByIdInternal,
// --- Post Functions --- //
getPostsFromDB as getPostsFromDBInternal,
getUserByUsername as getUserByUsernameInternal,
// --- Chat Functions --- //
getUserChats as getUserChatsInternal,
likeComment as likeCommentInternal,
likePost as likePostInternal,
// --- Mapper Functions --- //
queryDatabase as queryDatabaseInternal,
// getAllUserInfoByID as getAllUserInfoByIDInternal,
// Accidentally deleted function...
registerUser as registerUserInternal,
updateComment as updateCommentInternal,
updatePost as updatePostInternal,
} from "./helpers/utils/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 // from SQLite doesn't like relative paths, so I use this as
// A Workaround // A Workaround
const __dirname:string = dirname(fromFileUrl(import.meta.url)); const _dirname: string = dirname(fromFileUrl(import.meta.url));
const dbPath:string = join(__dirname, "../database/esp-projekt.sqlite"); const dbPath: string = join(_dirname, "../database/esp-projekt.sqlite");
const db = new DB(dbPath); const db = new DB(dbPath);
// Interfaces // +++ INTERFACES --------------------------------------------------- //
interface Post { // Only re-export interfaces that are needed by external code
posts_uuid: number; export type {
user_id: number; Accounts,
created_at: string; Chat,
post_text: string; Comments,
likes: number; Message,
comments: number; Post,
} from "./helpers/interfaces.ts";
// +++ HELPER FUNCTIONS --------------------------------------------- //
export function queryDatabase<T>(
query: string,
params: any[],
mapRow: (row: any) => T,
): Promise<T[]> {
return queryDatabaseInternal(db, query, params, mapRow);
} }
// +++ DATABASE INITIALIZATION -------------------------------------- //
export async function ensureDatabaseExists(): Promise<void> {
const dbDir = dirname(dbPath);
let dbInstance: DB | null = null; // Avoids hard to decode errors because it Throws one if it cant continue.
async function getPostsFromDB() { try {
let dataresult: Array<Post> = [];
try { try {
const rows = await db.query("SELECT * FROM posts"); await Deno.stat(dbDir);
// Assuming `db.query` returns an array of arrays or tuples
for (const row of rows) {
const [posts_uuid, user_id, created_at, post_text, likes, comments] = row;
dataresult.push({
posts_uuid: Number(posts_uuid), // Convert to string if necessary
user_id: Number(user_id),
created_at: String(created_at), // Convert to Date if necessary
post_text: String(post_text),
likes: Number(likes), // Convert to number if necessary
comments: Number(comments), // Convert to number if necessary
});
}
} catch (error) { } catch (error) {
console.error("Error fetching posts", error); if (error instanceof Deno.errors.NotFound) { // Deno.errors.NotFound is a type of error that is thrown when a file or directory is not found.
// In short, It's a type, and it makes sure that the "error" variable is of type Deno.errors.NotFound.
console.log(`Created database directory: ${dbDir}`);
} else {
throw error;
}
} }
return dataresult; console.log(`Opening database connection: ${dbPath}`);
} dbInstance = new DB(dbPath);
// Test Function, not useful
async function countPosts(): Promise<number> {
let count = 0;
try { try {
for (const [c] of await db.query("SELECT COUNT(*) FROM posts")) { dbInstance.query("SELECT 1 FROM marker LIMIT 1;");
count = c as number; console.log("Database already initialized (marker table found).");
}
} catch (error) { } catch (error) {
console.error("Error counting posts:", error); if (error instanceof Error) {
console.log(
"Marker table not found or query failed. Initializing database tables.",
);
db_create.createDatabase(dbInstance);
db_create.insertSampleData(dbInstance);
console.log("Database initialization complete.");
} else {
throw error;
}
} }
console.log("Total posts:", count); } catch (error) {
return count; console.error(
} "Error during database existence check or initialization:",
error,
function getCommentsForPost(postid: bigint) { );
} } finally {
if (dbInstance) {
function getCommentsForComments(commentid: bigint) { dbInstance.close();
} console.log("Database connection closed.");
} else {
function getAllUsers() { console.log("Database connection was not opened.");
const users = [];
for (
const [
user_id,
user_group,
bio,
displayname,
username,
user_email,
password,
firstname,
surname,
account_created,
blocked_users,
followers,
following,
contacts,
] of db.query("SELECT * FROM Accounts")
) {
users.push({
user_id,
user_group,
bio,
displayname,
username,
user_email,
password,
firstname,
surname,
account_created,
blocked_users,
followers,
following,
contacts,
});
} }
return users; }
} }
function getUserByID(userid: bigint) { // +++ ACCOUNT FUNCTIONS -------------------------------------------- //
} export const getAllUsersFromDB = () => getAllUsersFromDBInternal(db);
export const getUserByUsername = (username: string) =>
getUserByUsernameInternal(db, username);
// export const getAllUserInfoByID = (user_id: string) => getAllUserInfoByIDInternal(db, user_id);
export const registerUser = (
user: string,
password: string,
salt: string,
userGroup: string,
displayname: string,
user_email: string,
firstname: string,
surname: string,
account_created: string,
) =>
registerUserInternal(
db,
user,
password,
salt,
userGroup,
displayname,
user_email,
firstname,
surname,
account_created,
);
function getAllPostsFromUser() { // +++ POST FUNCTIONS ----------------------------------------------- //
} export const getPostsFromDB = (user_uuid?: string) =>
getPostsFromDBInternal(db, user_uuid);
export const getPostById = (postId: string) => getPostByIdInternal(db, postId);
export const createPost = (
userId: string,
createdAt: string,
postText: string,
postType: string,
) => createPostInternal(db, userId, createdAt, postText, postType);
export const updatePost = (
postId: string,
postText?: string,
postType?: string,
) => updatePostInternal(db, postId, postText, postType);
export const deletePost = (postId: string) => deletePostInternal(db, postId);
export const likePost = (postId: string, userId: string) =>
likePostInternal(db, postId, userId);
// Filter Functions // +++ COMMENT FUNCTIONS -------------------------------------------- //
function filterForImagePosts() { export const getCommentsFromDB = (post_id?: number) =>
return []; getCommentsFromDBInternal(db, post_id);
} // export const getCommentsForComments = (comment_id: number) => getCommentsForCommentsInternal(db, comment_id);
export const createComment = (
postId: string,
userId: string,
createdAt: string,
text: string,
) => createCommentInternal(db, postId, userId, createdAt, text);
export const updateComment = (commentId: string, text: string) =>
updateCommentInternal(db, commentId, text);
export const deleteComment = (commentId: string) =>
deleteCommentInternal(db, commentId);
export const likeComment = (commentId: string, userId: string) =>
likeCommentInternal(db, commentId, userId);
function filterForVideoPosts() { // +++ CHAT FUNCTIONS ----------------------------------------------- //
return []; export const getUserChats = (userId: string) =>
} getUserChatsInternal(db, userId);
export const getChatById = (chatId: string) => getChatByIdInternal(db, chatId);
export const getChatMessages = (chatId: string) =>
getChatMessagesInternal(db, chatId);
export const createChat = (participants: string[], chatName: string) =>
createChatInternal(db, participants, chatName);
export const addMessageToChat = (
chatId: string,
senderId: string,
content: string,
) => addMessageToChatInternal(db, chatId, senderId, content);
export const deleteChat = (chatId: string) => deleteChatInternal(db, chatId);
function filterForTextPosts() { // +++ UTILITY FUNCTIONS -------------------------------------------- //
return []; export { filterPosts };
}
// Export all Functions to make this a module
export {
getPostsFromDB,
countPosts,
getCommentsForPost,
getCommentsForComments,
getAllUsers,
getUserByID,
getAllPostsFromUser,
filterForImagePosts,
filterForVideoPosts,
filterForTextPosts
};

View File

@@ -1,6 +1,6 @@
{ {
"tasks": { "tasks": {
"dev": "deno task dev:api && deno task dev:vite", "dev": "deno task dev:api & deno task dev:vite",
"dev:api": "deno run -A api/main.ts", "dev:api": "deno run -A api/main.ts",
"dev:vite": "deno run -A npm:vite", "dev:vite": "deno run -A npm:vite",
"build": "deno run -A --node-modules-dir npm:vite build", "build": "deno run -A --node-modules-dir npm:vite build",

180
deno.lock generated
View File

@@ -6,6 +6,7 @@
"jsr:@std/bytes@1": "1.0.2", "jsr:@std/bytes@1": "1.0.2",
"jsr:@std/bytes@^1.0.2": "1.0.2", "jsr:@std/bytes@^1.0.2": "1.0.2",
"jsr:@std/crypto@1": "1.0.3", "jsr:@std/crypto@1": "1.0.3",
"jsr:@std/encoding@*": "1.0.5",
"jsr:@std/encoding@1": "1.0.5", "jsr:@std/encoding@1": "1.0.5",
"jsr:@std/encoding@^1.0.5": "1.0.5", "jsr:@std/encoding@^1.0.5": "1.0.5",
"jsr:@std/http@1": "1.0.9", "jsr:@std/http@1": "1.0.9",
@@ -22,12 +23,17 @@
"npm:autoprefixer@^10.4.20": "10.4.20_postcss@8.4.47", "npm:autoprefixer@^10.4.20": "10.4.20_postcss@8.4.47",
"npm:path-to-regexp@6.2.1": "6.2.1", "npm:path-to-regexp@6.2.1": "6.2.1",
"npm:postcss@^8.4.47": "8.4.47", "npm:postcss@^8.4.47": "8.4.47",
"npm:tailwind-scrollbar-hide@2": "2.0.0_tailwindcss@3.4.14__postcss@8.4.47",
"npm:tailwind-scrollbar@^4.0.1": "4.0.1_tailwindcss@4.0.15",
"npm:tailwindcss@*": "3.4.14_postcss@8.4.47", "npm:tailwindcss@*": "3.4.14_postcss@8.4.47",
"npm:tailwindcss@^3.4.14": "3.4.14_postcss@8.4.47", "npm:tailwindcss@^3.4.14": "3.4.14_postcss@8.4.47",
"npm:typescript@^5.8.2": "5.8.2",
"npm:url@~0.11.4": "0.11.4", "npm:url@~0.11.4": "0.11.4",
"npm:vite-plugin-checker@~0.9.1": "0.9.1_vite@5.4.10_@types+node@22.5.4",
"npm:vite@*": "5.4.10", "npm:vite@*": "5.4.10",
"npm:vite@^5.4.10": "5.4.10", "npm:vite@^5.4.10": "5.4.10",
"npm:vue-router@4": "4.4.5_vue@3.5.12", "npm:vue-router@4": "4.4.5_vue@3.5.12",
"npm:vue-router@^4.5.0": "4.5.0_vue@3.5.12_typescript@5.8.2",
"npm:vue@*": "3.5.12", "npm:vue@*": "3.5.12",
"npm:vue@^3.5.12": "3.5.12" "npm:vue@^3.5.12": "3.5.12"
}, },
@@ -78,6 +84,14 @@
"@alloc/quick-lru@5.2.0": { "@alloc/quick-lru@5.2.0": {
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==" "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="
}, },
"@babel/code-frame@7.26.2": {
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"dependencies": [
"@babel/helper-validator-identifier",
"js-tokens",
"picocolors"
]
},
"@babel/helper-string-parser@7.25.9": { "@babel/helper-string-parser@7.25.9": {
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="
}, },
@@ -100,7 +114,7 @@
"@deno/vite-plugin@1.0.0_vite@5.4.10": { "@deno/vite-plugin@1.0.0_vite@5.4.10": {
"integrity": "sha512-Q9UeWqs3s7B5lqzu1Z5QrzYAzqTj3+F9YW17tWobGRbT2G40ihwis6zK/+QgMgcG4fm3IqdIfXmpQYhkZpdMfw==", "integrity": "sha512-Q9UeWqs3s7B5lqzu1Z5QrzYAzqTj3+F9YW17tWobGRbT2G40ihwis6zK/+QgMgcG4fm3IqdIfXmpQYhkZpdMfw==",
"dependencies": [ "dependencies": [
"vite" "vite@5.4.10"
] ]
}, },
"@esbuild/aix-ppc64@0.21.5": { "@esbuild/aix-ppc64@0.21.5": {
@@ -290,11 +304,14 @@
"undici-types" "undici-types"
] ]
}, },
"@types/prismjs@1.26.5": {
"integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="
},
"@vitejs/plugin-vue@5.1.4_vite@5.4.10_vue@3.5.12": { "@vitejs/plugin-vue@5.1.4_vite@5.4.10_vue@3.5.12": {
"integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==",
"dependencies": [ "dependencies": [
"vite", "vite@5.4.10",
"vue" "vue@3.5.12"
] ]
}, },
"@vue/compiler-core@3.5.12": { "@vue/compiler-core@3.5.12": {
@@ -365,7 +382,15 @@
"dependencies": [ "dependencies": [
"@vue/compiler-ssr", "@vue/compiler-ssr",
"@vue/shared", "@vue/shared",
"vue" "vue@3.5.12"
]
},
"@vue/server-renderer@3.5.12_vue@3.5.12_vue@3.5.12__typescript@5.8.2": {
"integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==",
"dependencies": [
"@vue/compiler-ssr",
"@vue/shared",
"vue@3.5.12_typescript@5.8.2"
] ]
}, },
"@vue/shared@3.5.12": { "@vue/shared@3.5.12": {
@@ -393,7 +418,7 @@
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dependencies": [ "dependencies": [
"normalize-path", "normalize-path",
"picomatch" "picomatch@2.3.1"
] ]
}, },
"arg@5.0.2": { "arg@5.0.2": {
@@ -464,9 +489,18 @@
"is-binary-path", "is-binary-path",
"is-glob", "is-glob",
"normalize-path", "normalize-path",
"readdirp" "readdirp@3.6.0"
] ]
}, },
"chokidar@4.0.3": {
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dependencies": [
"readdirp@4.1.1"
]
},
"clsx@2.1.1": {
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
},
"color-convert@2.0.1": { "color-convert@2.0.1": {
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": [ "dependencies": [
@@ -482,7 +516,7 @@
"cross-spawn@7.0.3": { "cross-spawn@7.0.3": {
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dependencies": [ "dependencies": [
"path-key", "path-key@3.1.1",
"shebang-command", "shebang-command",
"which" "which"
] ]
@@ -581,6 +615,12 @@
"reusify" "reusify"
] ]
}, },
"fdir@6.4.3_picomatch@4.0.2": {
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"dependencies": [
"picomatch@4.0.2"
]
},
"fill-range@7.1.1": { "fill-range@7.1.1": {
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": [ "dependencies": [
@@ -700,6 +740,9 @@
"jiti@1.21.6": { "jiti@1.21.6": {
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==" "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w=="
}, },
"js-tokens@4.0.0": {
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"lilconfig@2.1.0": { "lilconfig@2.1.0": {
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==" "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="
}, },
@@ -725,7 +768,7 @@
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": [ "dependencies": [
"braces", "braces",
"picomatch" "picomatch@2.3.1"
] ]
}, },
"minimatch@9.0.5": { "minimatch@9.0.5": {
@@ -757,6 +800,13 @@
"normalize-range@0.1.2": { "normalize-range@0.1.2": {
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==" "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="
}, },
"npm-run-path@6.0.0": {
"integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
"dependencies": [
"path-key@4.0.0",
"unicorn-magic"
]
},
"object-assign@4.1.1": { "object-assign@4.1.1": {
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
}, },
@@ -772,6 +822,9 @@
"path-key@3.1.1": { "path-key@3.1.1": {
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
}, },
"path-key@4.0.0": {
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="
},
"path-parse@1.0.7": { "path-parse@1.0.7": {
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
}, },
@@ -791,6 +844,9 @@
"picomatch@2.3.1": { "picomatch@2.3.1": {
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
}, },
"picomatch@4.0.2": {
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="
},
"pify@2.3.0": { "pify@2.3.0": {
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
}, },
@@ -846,6 +902,14 @@
"source-map-js" "source-map-js"
] ]
}, },
"prism-react-renderer@2.4.1_react@19.0.0": {
"integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==",
"dependencies": [
"@types/prismjs",
"clsx",
"react"
]
},
"punycode@1.4.1": { "punycode@1.4.1": {
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
}, },
@@ -858,6 +922,9 @@
"queue-microtask@1.2.3": { "queue-microtask@1.2.3": {
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
}, },
"react@19.0.0": {
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="
},
"read-cache@1.0.0": { "read-cache@1.0.0": {
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dependencies": [ "dependencies": [
@@ -867,9 +934,12 @@
"readdirp@3.6.0": { "readdirp@3.6.0": {
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dependencies": [ "dependencies": [
"picomatch" "picomatch@2.3.1"
] ]
}, },
"readdirp@4.1.1": {
"integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw=="
},
"resolve@1.22.8": { "resolve@1.22.8": {
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dependencies": [ "dependencies": [
@@ -990,12 +1060,25 @@
"supports-preserve-symlinks-flag@1.0.0": { "supports-preserve-symlinks-flag@1.0.0": {
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
}, },
"tailwind-scrollbar-hide@2.0.0_tailwindcss@3.4.14__postcss@8.4.47": {
"integrity": "sha512-lqiIutHliEiODwBRHy4G2+Tcayo2U7+3+4frBmoMETD72qtah+XhOk5XcPzC1nJvXhXUdfl2ajlMhUc2qC6CIg==",
"dependencies": [
"tailwindcss@3.4.14_postcss@8.4.47"
]
},
"tailwind-scrollbar@4.0.1_tailwindcss@4.0.15": {
"integrity": "sha512-j2ZfUI7p8xmSQdlqaCxEb4Mha8ErvWjDVyu2Ke4IstWprQ/6TmIz1GSLE62vsTlXwnMLYhuvbFbIFzaJGOGtMg==",
"dependencies": [
"prism-react-renderer",
"tailwindcss@4.0.15"
]
},
"tailwindcss@3.4.14_postcss@8.4.47": { "tailwindcss@3.4.14_postcss@8.4.47": {
"integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
"dependencies": [ "dependencies": [
"@alloc/quick-lru", "@alloc/quick-lru",
"arg", "arg",
"chokidar", "chokidar@3.6.0",
"didyoumean", "didyoumean",
"dlv", "dlv",
"fast-glob", "fast-glob",
@@ -1017,6 +1100,9 @@
"sucrase" "sucrase"
] ]
}, },
"tailwindcss@4.0.15": {
"integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg=="
},
"thenify-all@1.6.0": { "thenify-all@1.6.0": {
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dependencies": [ "dependencies": [
@@ -1029,6 +1115,16 @@
"any-promise" "any-promise"
] ]
}, },
"tiny-invariant@1.3.3": {
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
},
"tinyglobby@0.2.12_picomatch@4.0.2": {
"integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
"dependencies": [
"fdir",
"picomatch@4.0.2"
]
},
"to-regex-range@5.0.1": { "to-regex-range@5.0.1": {
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": [ "dependencies": [
@@ -1038,9 +1134,15 @@
"ts-interface-checker@0.1.13": { "ts-interface-checker@0.1.13": {
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
}, },
"typescript@5.8.2": {
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="
},
"undici-types@6.19.8": { "undici-types@6.19.8": {
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
}, },
"unicorn-magic@0.3.0": {
"integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="
},
"update-browserslist-db@1.1.1_browserslist@4.24.2": { "update-browserslist-db@1.1.1_browserslist@4.24.2": {
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
"dependencies": [ "dependencies": [
@@ -1059,6 +1161,21 @@
"util-deprecate@1.0.2": { "util-deprecate@1.0.2": {
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
}, },
"vite-plugin-checker@0.9.1_vite@5.4.10_@types+node@22.5.4": {
"integrity": "sha512-neH3CSNWdkZ+zi+WPt/0y5+IO2I0UAI0NX6MaXqU/KxN1Lz6np/7IooRB6VVAMBa4nigqm1GRF6qNa4+EL5jDQ==",
"dependencies": [
"@babel/code-frame",
"chokidar@4.0.3",
"npm-run-path",
"picocolors",
"picomatch@4.0.2",
"strip-ansi@7.1.0",
"tiny-invariant",
"tinyglobby",
"vite@5.4.10_@types+node@22.5.4",
"vscode-uri"
]
},
"vite@5.4.10": { "vite@5.4.10": {
"integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
"dependencies": [ "dependencies": [
@@ -1068,11 +1185,31 @@
"rollup" "rollup"
] ]
}, },
"vite@5.4.10_@types+node@22.5.4": {
"integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
"dependencies": [
"@types/node",
"esbuild",
"fsevents",
"postcss",
"rollup"
]
},
"vscode-uri@3.1.0": {
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="
},
"vue-router@4.4.5_vue@3.5.12": { "vue-router@4.4.5_vue@3.5.12": {
"integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==", "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==",
"dependencies": [ "dependencies": [
"@vue/devtools-api", "@vue/devtools-api",
"vue" "vue@3.5.12"
]
},
"vue-router@4.5.0_vue@3.5.12_typescript@5.8.2": {
"integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==",
"dependencies": [
"@vue/devtools-api",
"vue@3.5.12_typescript@5.8.2"
] ]
}, },
"vue@3.5.12": { "vue@3.5.12": {
@@ -1081,10 +1218,21 @@
"@vue/compiler-dom", "@vue/compiler-dom",
"@vue/compiler-sfc", "@vue/compiler-sfc",
"@vue/runtime-dom", "@vue/runtime-dom",
"@vue/server-renderer", "@vue/server-renderer@3.5.12_vue@3.5.12",
"@vue/shared" "@vue/shared"
] ]
}, },
"vue@3.5.12_typescript@5.8.2": {
"integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==",
"dependencies": [
"@vue/compiler-dom",
"@vue/compiler-sfc",
"@vue/runtime-dom",
"@vue/server-renderer@3.5.12_vue@3.5.12_vue@3.5.12__typescript@5.8.2",
"@vue/shared",
"typescript"
]
},
"which@2.0.2": { "which@2.0.2": {
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dependencies": [ "dependencies": [
@@ -1120,6 +1268,7 @@
"remote": { "remote": {
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
"https://deno.land/std@0.224.0/fs/exists.ts": "3d38cb7dcbca3cf313be343a7b8af18a87bddb4b5ca1bd2314be12d06533b50f",
"https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8",
"https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2",
"https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", "https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c",
@@ -1266,7 +1415,12 @@
"packageJson": { "packageJson": {
"dependencies": [ "dependencies": [
"npm:@deno/vite-plugin@1", "npm:@deno/vite-plugin@1",
"npm:@vitejs/plugin-vue@^5.1.4" "npm:@vitejs/plugin-vue@^5.1.4",
"npm:tailwind-scrollbar-hide@2",
"npm:tailwind-scrollbar@^4.0.1",
"npm:typescript@^5.8.2",
"npm:vite-plugin-checker@~0.9.1",
"npm:vue-router@^4.5.0"
] ]
} }
} }

View File

@@ -1,13 +1,13 @@
<!doctype html> <!Doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/png" href="src/assets/esp-logo_no_text.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title> <title>ESP - Express, Share, Post</title>
<link rel="stylesheet" href="/src/assets/main.css" /> <link rel="stylesheet" href="/src/assets/main.css" />
</head> </head>
<body> <body class="bg-hintergrund-farbe sm:overflow-hidden">
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>

991
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,13 @@
{ {
"dependencies": { "dependencies": {
"@deno/vite-plugin": "^1.0.0", "@deno/vite-plugin": "^1.0.0",
"@vitejs/plugin-vue": "^5.1.4" "@vitejs/plugin-vue": "^5.1.4",
"tailwind-scrollbar": "^4.0.1",
"tailwind-scrollbar-hide": "^2.0.0",
"vue-router": "^4.5.0"
},
"devDependencies": {
"typescript": "^5.8.2",
"vite-plugin-checker": "^0.9.1"
} }
} }

View File

@@ -1,3 +1,3 @@
<template> <template >
<router-view /> <router-view />
</template> </template>

BIN
src/assets/icons/logout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

BIN
src/assets/icons/other.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

BIN
src/assets/icons/send.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

BIN
src/assets/icons/shield.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

BIN
src/assets/icons/trash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

BIN
src/assets/icons/x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

View File

@@ -1,3 +1,5 @@
@plugin 'tailwind-scrollbar';
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;

View File

@@ -4,21 +4,18 @@ import Contacts from "./home_components/contacts.vue";
import Legal from "./home_components/legal.vue"; import Legal from "./home_components/legal.vue";
import Navigationbar from "./home_components/navigationbar.vue"; import Navigationbar from "./home_components/navigationbar.vue";
import Trending from "./home_components/trending.vue"; import Trending from "./home_components/trending.vue";
import QuickSearch from "./home_components/quick_search.vue";
</script> </script>
<template> <template>
<div id="main" class="bg-hintergrund-farbe flex"> <div id="main" class="sm:flex overflow-y-auto sm:h-full sm:scrollbar">
<div id="left" class="border border-b-grau w-72"> <div id="left" class="sm:w-72 min-w-72">
<navigationbar></navigationbar> <navigationbar></navigationbar>
<trending></trending>
</div> </div>
<div class="w-100p"> <div class="sm:w-100p w-screen">
<feed></feed> <feed></feed>
</div> </div>
<div class="w-1/4"> <div class="sm:w-1/4 w-screen">
<quick-search></quick-search>
<contacts></contacts> <contacts></contacts>
<legal></legal> <legal></legal>
</div> </div>

View File

@@ -2,15 +2,21 @@
import Legal from "./home_components/legal.vue"; import Legal from "./home_components/legal.vue";
import Login_comp from "./login_components/login_comp.vue"; import Login_comp from "./login_components/login_comp.vue";
import router from "../router";
function route_home() {
router.push("/");
}
</script> </script>
<template> <template>
<div id="main" class="bg-hintergrund-farbe flex p-2 "> <div id="main" class="bg-hintergrund-farbe sm:flex sm:p-2 sm:justify-between ">
<div class="items-center pr-96 pl-2 pt-5"> <div class="pt-5 pl-2">
<img src="../assets/esp-logo_no_text.png" alt="" class="rounded-lg h-12 w-24 mx-auto"> <img src="../assets/esp-logo_no_text.png" alt="" class=" rounded-lg h-12 w-24 hover:shadow-2xl hover:shadow-grau-dunkel" @click="route_home">
</div> </div>
<login_comp></login_comp> <login_comp class="inset-0"></login_comp>
<div class="p-5 "> <div class="md:inset-y-0 sm:right-0 sm:max-w-36 text-center">
<legal></legal> <legal></legal>
</div> </div>
</div> </div>

View File

@@ -1,38 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

View File

@@ -1,9 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
const contacts =[ import { ref } from 'vue';
{display_name: "Linux Enjoyer", username: "lunix"}, import Popup_chat from "./popup_chat.vue";
{display_name: "XBOX", username: "Xbox"},
{display_name: "JETBrains", username: "Jetbrains"}, const contacts = [
] { display_name: "Linux Enjoyer", username: "lunix" },
{ display_name: "XBOX", username: "xbox" },
{ display_name: "JETBrains", username: "jetbrains" },
{ display_name: "klopupser13", username: "theopampa",}
];
const selectedContact = ref(null);
const showChatPopup = ref(false);
function openChat(contact) {
selectedContact.value = contact;
showChatPopup.value = true;
}
</script> </script>
<template> <template>
@@ -13,19 +25,19 @@ const contacts =[
</div> </div>
<div> <!--CONTENT--> <div> <!--CONTENT-->
<ul class="space-y-1"> <ul class="space-y-1">
<li v-for="(contact) in contacts" :key="contact" class="flex rounded-lg"> <li v-for="(contact) in contacts" :key="contact.username" @click="openChat(contact)" class="flex rounded-lg p-2 cursor-pointer">
<!--CONTACT--> <!--CONTACT-->
<img src="../../assets/default_pp.png" alt="" class="w-12 h-12 mr-2"> <img src="../../assets/default_pp.png" alt="" class="w-12 h-12 mr-2"> <!--PROFILBILD-->
<div class=""> <div class="">
<label class=" font-bold m-1 text-weiss">{{ contact.display_name }}</label><br> <label class="font-bold m-1 text-weiss">{{ contact.display_name }}</label><br> <!-- display name-->
<p class="text-base m-1 text-grau2 underline-offset-3">@{{ contact.username }}</p> <p class="text-base m-1 text-grau2 underline-offset-3">@{{ contact.username }}</p> <!-- username-->
</div> </div>
</li> </li>
</ul> </ul>
</div> </div>
<popup_chat v-if="showChatPopup" :contact="selectedContact" @close="showChatPopup = false" />
</div> </div>
</template> </template>
<style scoped> <style scoped>
</style> </style>

View File

@@ -1,105 +1,176 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import {onMounted, ref} from "vue";
// import {valueOf} from "tailwindcss"; import router from "../../router";
const post = ref([ // PLACEHOLDER
{id: 1, const upc = ref([]); // user post computed
profile_picture: "../../assets/default_pp.png", let post_create_text = "";
author_display_name: "Linux Enjoyer",author_username: "lunix", let self_id ;
content:"I love Linux. My Favorite Linux Distro is ARCH LINUX.",
comments_count: 1, likes: 5},
{id: 2,
profile_picture: "../../assets/default_pp.png",
author_display_name: "XBOX",author_username: "Xbox",
content: "Call of Duty: Black Ops 6 is OUT NOW.",
comments_count: 500000, likes: 100000},
{id: 3,
profile_picture: "../../assets/default_pp.png",
author_display_name: "JETBrains",author_username: "Jetbrains",
content: "BLI BLA BLUB. Jetbrains is the best IDE." ,
comments_count: 5000, likes: 1000},
{id: 4,
profile_picture: "../../assets/default_pp.png",
author_display_name: "GITHUB", author_username: "GitHub",
content: "GitHub Copilot got an massive update. Check out the new features.",
comments_count: 1500000, likes: 500000},
{id: 5,
profile_picture: "../../assets/danielvici_pp.png",
author_display_name: "danielvici123", author_username: "danielvici",
content: "I created this WebApp with VUE3 and TailwindCSS. It was a lot of fun.",
comments_count: undefined, likes: 532844},
{id: 6,
profile_picture: "../../assets/default_pp.png",
author_display_name: "Microsoft", author_username: "Microsoft",
content: "Windows 11 24H2 is out now. Learn more here: https://www.microsoft.com",
comments_count: 500000, likes: 100000},
{id: 7,
profile_picture: "../../assets/default_pp.png",
author_display_name: "Apple", author_username: "Apple",
content: "The new iPhone 16 is out now. Everything you need to know: https://www.apple.com",
comments_count: 500000, likes: 100000},
])
const addLike = (index: number) => { onMounted(async () => {
post.value[index].likes += 1; self_id = localStorage.getItem("self_id");
console.log("New Like Amount: ", post.value[index].likes); await createFeed();
});
} async function createFeed() {
function post_publish_func(text:string) { try {
console.log("Post: ", text); // posts und user holen und schauen ob sie richtig sidn
if (text === undefined || text === "") { const post_response = await fetch('http://localhost:8000/api/posts', { method: 'GET' });
console.log("Post is empty"); const postsDATA = await post_response.json();
alert("Post is empty");
const user_response = await fetch('http://localhost:8000/api/users', { method: 'GET' });
const usersDATA = await user_response.json();
if(post_response.status === 404 || user_response.status === 404) {
console.error("ERRROR");
await router.push('/');
return; return;
} else {
console.log("Post is not empty");
post.value.push({id: post.value.length + 1, profile_picture: "../../assets/danielvici_pp.png", author_display_name: "ADMIN", author_username: "esp_admin", content: text, comments_count: undefined, likes: 0});
} }
// posts und user kombinieren
const combinedPosts = postsDATA.map(post => {
const user = usersDATA.find(user => user.user_id === post.user_id);
return {
post_id: post.posts_uuid,
post_text: post.post_text,
likes: post.likes,
comments: post.comments,
displayname: user ? user.displayname : 'Unknown',
username: user ? user.username : 'unknown_user',
user_id: post.user_id,
};
});
//upc.value = combinedPosts;
upc.value = combinedPosts.sort((a, b) => b.post_id - a.post_id);;
} catch (e) {
console.error("An error has occurred. Please try again later.");
console.error(e);
} }
console.log(upc.value);
}
async function addLike(post_id: string | number, user_id: number, index: number) {
try {
console.log("UPC: ", upc.value);
console.log("post_id: ", post_id);
upc.value[index].likes++;
const response = await fetch(`http://localhost:8000/api/post/${post_id}/like`, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: `{"userId":${user_id}}`,
});
console.log('Antwort-Status:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('Server-Fehlertext:', errorText);
//upc.value[index].likes--;
throw new Error(`HTTP error! status: ${response.status}, text: ${errorText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fehler beim Liken des Posts:', error);
throw error;
}
}
async function post_create(post_text: string) {
if (post_text === "") {
alert("Please write something before posting.");
return;
}
console.log(self_id);
console.log(post_text);
const response = await fetch(`http://localhost:8000/api/post/create`, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: `{"userId":${self_id},"postText":"${post_create_text}","postType":"text"}`});
const data = await response.json();
if (response.ok) {
console.log(response.text);
await createFeed();
}
console.log(data);
console.log(post_text);
}
function gotoPost(post_id: string | number) {
localStorage.setItem("viewedpost", post_id.toString());
router.push(`/post/${post_id}`);
}
function copyLink(post_id: string | number) {
const tocopy = `http://localhost:5173/post/${post_id}`;
navigator.clipboard.writeText(tocopy);
alert("Copied to clipboard");
}
function gotoProfile(user_id: string | number) {
router.push(`/profile/${user_id}`);
}
</script> </script>
<template> <template>
<div class="border-x-2 border-x-grau2"> <!-- MAIN --> <div class="sm:border-x sm:border-x-grau2"> <!-- MAIN -->
<div> <!-- FEED HEADER --> <div> <!-- FEED HEADER -->
<h2 class="align-middle p-6 text-xl font-bold text-grau2 border-b-grau2 border-b ">Feed</h2> <h2 class="align-middle p-6 text-3xl text-weiss border-b-grau2 border-b-2 ">Feed</h2>
<!-- POSTING--> <!-- POSTING-->
<div class="flex border-2 border-b-grau2"> <div class="flex border-2 border-b-grau2">
<img src="../../assets/danielvici_pp.png" alt="" class="p-2 rounded-full w-16 h-16"> <img v-if="self_id != '99' " src="../../assets/default_pp.png" alt="" class="rounded-full w-16 h-16">
<form> <img v-else src="../../assets/danielvici_pp.png" alt="" class="rounded-full w-16 h-16">
<form class="w-full pr-5">
<!-- post_publish ist richtig aber wird falsch angezeigt. File Input geht nicht--> <!-- post_publish ist richtig aber wird falsch angezeigt. File Input geht nicht-->
<textarea v-model="post_publish" name="post_text" class="bg-hintergrund-farbe rounded-lg m-2 p-1 focus:outline-none text-grau2 w-200p resize-none" rows="3" placeholder="Write something..."></textarea> <textarea v-model="post_create_text" name="post_text" class="bg-hintergrund-farbe rounded-lg m-2 p-1 focus:outline-none text-grau2 w-full resize-none" rows="3" placeholder="Write something..."></textarea>
<div class=""> <div class="">
<input class="text-weiss" type="file" accept=".png, .jpg, .jpeg"> <button id="post_publish" name="post_publishss" class="text-weiss p-1 m-2 rounded-lg py-3 px-5 bg-button-farbe" @click.prevent="post_create(post_create_text)" type="button">Post</button>
<button id="post_publish" name="post_publishss" class="text-weiss p-1 m-2 rounded-lg py-3 px-5 bg-button-farbe" @click.prevent="post_publish_func(post_publish)" type="button">Post</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<div class="sm:overflow-y-auto sm:h-[650px] sm:scrollbar"> <!-- CONTENT -->
<div> <!-- CONTENT -->
<ul> <ul>
<li v-for="(postitem, indexus) in post" :key="post" class="border-2 border-b-grau2 p-3 flex"> <li v-for="(postitem, indexus) in upc" :key="upc" class="border-2 border-b-grau2 p-3 flex">
<!-- POST --> <!-- POST -->
<img src="../../assets/default_pp.png" alt="" class="rounded-full w-16 h-16">
<img v-if="postitem.username != 'danielvici' " src="../../assets/default_pp.png" alt="" class="rounded-full w-16 h-16">
<img v-else src="../../assets/danielvici_pp.png" alt="" class="rounded-full w-16 h-16">
<div> <div>
<div> <!-- POST HEADER --> <div> <!-- POST HEADER -->
<label class="text-lg font-bold m-1 text-weiss">{{postitem.author_display_name}}</label> <label class="text-lg font-bold m-1 text-weiss">{{postitem.displayname}}</label>
<label class="text-base m-1 underline-offset-3 text-grau2">@{{ postitem.author_username }}</label> <label class="text-base m-1 text-grau2">@{{ postitem.username }}</label>
</div> </div>
<div class="m-2"> <!-- POST CONTENT --> <div class="m-2"> <!-- POST CONTENT -->
<p class="text-sm m-1 text-weiss">{{ postitem.content }}</p> <p class="text-sm m-1 text-weiss">{{ postitem.post_text }}</p>
</div> </div>
<div class="flex"> <!-- POST FOOTER --> <div class="sm:flex"><!-- POST FOOTER -->
<div class="flex"> <!-- Comments --> <div class="flex">
<img src="../../assets/icons/comment.png" alt="" class="rounded-full align-middle"> <div class="flex"> <!-- Comments -->
<label class="text-sm m-1 text-weiss" v-if="postitem.comments_count != undefined">{{ postitem.comments_count }}</label> <img src="../../assets/icons/comment.png" alt="" class="rounded-full align-middle">
<label class="text-sm m-1 text-weiss" v-else>Comments disabled</label> <label class="text-sm m-1 text-weiss" v-if="postitem.comments != undefined">{{ postitem.comments }}</label>
</div> <label class="text-sm m-1 text-weiss" v-else>Comments disabled</label>
</div>
<div class="flex items-center" @click="addLike(indexus)"> <!-- Likes --> <div class="flex items-center" @click="addLike(postitem.post_id, postitem.user_id, indexus)"> <!-- Likes -->
<img alt="" src="../../assets/icons/herz.png" class="align-middle"> <img alt="" src="../../assets/icons/herz.png" class="align-middle">
<label class="text-sm m-1 text-weiss">{{ postitem.likes }}</label> <label class="text-sm m-1 text-weiss">{{ postitem.likes }}</label>
</div>
</div>
<br class="sm:hidden">
<div class="flex sm:tems-center mx-2"> <!-- View Post -->
<button @click="gotoPost(postitem.post_id)" class="text-schwarz mx-1 px-1 rounded-lg bg-button-farbe">View Post</button>
<button @click="copyLink(postitem.post_id)" class="text-schwarz pl-1 mx-1 px-1 rounded-lg bg-button-farbe">Share Post</button>
<button @click="gotoProfile(postitem.user_id)" class="text-schwarz pl-1 mx-1 px-1 rounded-lg bg-button-farbe"> Go to Profile</button>
</div><!-- ENDE --> </div><!-- ENDE -->
</div> </div>
</div> </div>

View File

@@ -1,18 +1,32 @@
<script setup lang="ts"> <script setup lang="ts">
import {useRoute, useRouter} from "vue-router";
const route = useRoute();
const router = useRouter();
function gotop() {
location.reload();
console.log("PARAMS: "+ route.path);
console.log("Zum Seitenanfang gescrollt");
}
</script> </script>
<template> <template>
<div class="text-grau2 p-3"> <div class="text-grau2 p-3 text-center">
<a href="">Terms of Service</a> <a href="">Terms of Service</a>
- -
<a href="">Privacy Policy</a><br> <a href="">Privacy Policy</a><br>
<a href="">Imprint</a> <a href="">Imprint</a>
- -
<a href="">Contact</a> <a href="https://esp-projekt.notion.site/191fb990f26c808298dad302e97fb299?pvs=105">Contact</a>
- -
<a href="">Support</a> <a href="https://esp-projekt.notion.site/191fb990f26c808298dad302e97fb299?pvs=105">Support</a>
-
<a href="https://icons8.com"> Icons by icons8.com</a>
<div class="sm:hidden flex justify-center pt-8"><button class="text-weiss p-1 m-2 rounded-lg py-3 px-5 bg-button-farbe transition duration-300 ease-in-out" @click="gotop">Back to Top</button></div>
</div> </div>
</template> </template>

View File

@@ -1,43 +1,74 @@
<script setup lang="ts"> <script setup lang="ts">
// Funktionen um die Seiten zu öffnen
// home -> app.vue // home -> app.vue
// PLACEHOLDER
import router from "../../router"; import router from "../../router";
import { ref, onMounted, onUnmounted } from 'vue';
const sb_home = () => { let self;
console.log("home"); const isMobile = ref(false);
router.push("/"); const show = ref(false);
}
const sb_search = () => {
console.log("Search");
}
const sb_notifications = () => {
console.log("Notifications");
}
const sb_messages = () => {
console.log("Messages");
}
const sb_accounts = () => {
console.log("Accounts");
}
const sb_settings = () => {
console.log("Settings");
}
const getShowMobileElements = () => {
const value = localStorage.getItem("mobile");
console.log("localStorage mobile:", value); // Debugging-Ausgabe
return value === 'true';
};
const setShowMobileElements = (value) => {
console.log("localStorage set mobile:", value.toString()); // Debugging-Ausgabe
localStorage.setItem("mobile", value.toString());
};
const toggleElements = () => {
if (isMobile.value){
show.value = !show.value;
setShowMobileElements(show.value);
}
};
const checkScreenWidth = () => {
isMobile.value = window.innerWidth < 640;
if(isMobile.value === false){
show.value = true;
} else {
show.value = false;
}
};
onMounted(() => {
checkScreenWidth();
window.addEventListener('resize', checkScreenWidth);
const show = ref(getShowMobileElements());
if(localStorage.getItem("mobile") === null){
show.value = false;
}
self = localStorage.getItem("self_id");
console.log("SELF NB: " + self);
});
onUnmounted(() => {
window.removeEventListener('resize', checkScreenWidth);
});
</script> </script>
<template> <template>
<div class="pt-4 border-b-2 border-b-grau2"> <div class="pt-4 border-b-2 border-b-grau2">
<div class="items-center flex justify-center"> <div class="items-center flex justify-center"><!-- BILD-->
<img src="../../assets/esp-logo_no_text.png" alt="" class="rounded-lg h-12 w-24 mx-auto" @click="sb_home"> <img src="../../assets/esp-logo_no_text.png" alt="" class="rounded-lg h-12 w-24 mx-auto hover:shadow-2xl hover:shadow-grau-dunkel" @click="router.push('/')">
</div> </div>
<div class="align-middle space-y-3 pt-3 pl-3 pb-4 font-bold text-xl"> <div class="sm:hidden text-weiss items-center flex justify-center mt-2 py-4 border-y-2 border-y-grau2 " v-if="isMobile">
<label class="flex text-center text-grau2 hover:bg-logo-farbe-lila shadow-2xl rounded-lg" @click="sb_home"><img class="pr-2" src="../../assets/icons/home.png" alt=""> Home</label> <button @click="toggleElements"> {{ show ? 'close navbar' : 'expand navbar' }} </button>
<label class="flex text-center text-grau2 hover:bg-logo-farbe-lila shadow-2xl rounded-lg" @click="sb_search"><img class="pr-2" src="../../assets/icons/lupe.png" alt="">Search</label> </div>
<label class="flex text-center text-grau2 hover:bg-logo-farbe-rot shadow-2xl rounded-lg" @click="sb_notifications"><img class="pr-2" src="../../assets/icons/glocke.png" alt="">Notifications</label> <div class="align-middle space-y-3 pt-4 pl-3 pb-4 pr-4 font-bold text-xl" v-if="show || !isMobile"> <!-- Icons (Bild) und Text Damit der Text weiß ist muss zwei mal gedrückt werden manchmal-->
<label class="flex text-center text-grau2 hover:bg-logo-farbe-rot shadow-2xl rounded-lg" @click="sb_messages"><img class="pr-2" src="../../assets/icons/mail.png" alt="">Messages</label> <label class="flex text-center text-grau2 hover:bg-logo-farbe-lila rounded-lg" @click="router.push('/')"><img class="mr-2 p-1 bg-logo-farbe-lila rounded-lg" src="../../assets/icons/home.png" alt=""> Home</label>
<label class="flex text-center text-grau2 hover:bg-logo-farbe-blau shadow-2xl rounded-lg" @click="sb_accounts"><img class="pr-2" src="../../assets/icons/user.png" alt="">Profile</label> <label class="flex text-center text-grau2 hover:bg-logo-farbe-lila rounded-lg" @click="router.push('/search')"> <img class="mr-2 p-1 bg-logo-farbe-lila rounded-lg" src="../../assets/icons/lupe.png" alt="">Search</label>
<label class="flex text-center text-grau2 hover:bg-logo-farbe-blau shadow-2xl rounded-lg" @click="sb_settings"><img class="pr-2" src="../../assets/icons/zahnrad.png" alt="">Settings</label> <label class="flex text-center text-grau2 hover:bg-logo-farbe-rot rounded-lg" @click="router.push('/notifications')"> <img class="mr-2 p-1 bg-logo-farbe-rot rounded-lg" src="../../assets/icons/glocke.png" alt="">Notifications</label>
<label class="flex text-center text-grau2 hover:bg-logo-farbe-rot rounded-lg" @click="router.push('/messages')"> <img class="mr-2 p-1 bg-logo-farbe-rot rounded-lg" src="../../assets/icons/mail.png" alt="">Messages</label>
<label class="flex text-center text-grau2 hover:bg-logo-farbe-blau rounded-lg" @click="router.push(`/profile/${self}`)"> <img class="mr-2 p-1 bg-logo-farbe-blau rounded-lg" src="../../assets/icons/user.png" alt="">Profile</label>
<label class="flex text-center text-grau2 hover:bg-logo-farbe-blau rounded-lg" @click="router.push('/settings')"> <img class="mr-2 p-1 bg-logo-farbe-blau rounded-lg" src="../../assets/icons/zahnrad.png" alt="">Settings</label>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -0,0 +1,112 @@
<script setup lang="ts">
import {defineProps, defineEmits, onMounted, watch, ref} from 'vue';
import { nextTick } from 'vue';
let current_contact = null;
let self = "danielvici123";
const nachrichten = [
{ display_name: "Linux Enjoyer", username: "lunix", message: "Moin", msg_id: 1, chat_id: 16423 },
{ display_name: "XBOX",username: "xbox", message: "Hey, was geht?", msg_id: 2, chat_id: 29874 },
{ display_name: "danielvici223", username: "danielvici123", message: "Alles gut, und bei dir?", msg_id: 3, chat_id: 29874 },
{ display_name: "JETBrains",username: "jetbrains", message: "Hat jemand Erfahrung mit IntelliJ?", msg_id: 4, chat_id: 41235 },
{ display_name: "danielvici223",username: "danielvici123", message: "Ja, was brauchst du?", msg_id: 5, chat_id: 41235 },
{ display_name: "JETBrains",username: "jetbrains", message: "Wie kann ich Plugins effizient verwalten?", msg_id: 6, chat_id: 41235 },
{ display_name: "klopupser13", username: "theopampa", message: "ja ok können wir machen", msg_id: 7, chat_id: 41245 },
{ display_name: "danielvici223",username: "danielvici123", message: "willst du fornite", msg_id: 7, chat_id: 41245 },
];
let geladeneNachrichten = ref([]);
const send_message = ref("");
const props = defineProps({
contact: Object
});
const emit = defineEmits(['close']);
function closeChat() {
emit('close');
}
function openCHAT() {
geladeneNachrichten.value = [];
console.log("Chat geöffnet");
// conact ist null dann abruch
if(!props.contact) return;
// contact ist der aktuelle kontakt
current_contact = props.contact ? { ...props.contact } : null;
console.log(current_contact);
/*nachrichten.forEach((nachricht) => {
if ((nachricht.username == current_contact.username || nachricht.username == self) && nachricht.chat_id == current_contact.chat_id) {
geladeneNachrichten.value.push(nachricht);
}
});
*/
geladeneNachrichten.value = nachrichten.filter((nachricht) => {
console.log(`Nachricht von ${nachricht.username} mit Chat-ID ${nachricht.chat_id}`);
return (
(nachricht.username === current_contact.username || nachricht.username === self) &&
nachricht.chat_id == current_contact.chat_id
);
});
console.log("Nach dem Filtern:", geladeneNachrichten.value);
console.log(geladeneNachrichten);
}
function sendMessage(event){
event.preventDefault();
// API nachricht senden
console.log("Nachricht gesendet")
}
onMounted(() => {
openCHAT();
console.log("Current Contact:", current_contact);
});
watch(() => props.contact, () => {
openCHAT();
console.log("Current Contact:", current_contact);
});
</script>
<template>
<div class="fixed bottom-0 right-0 m-3 p-4 bg-schwarz text-weiss rounded-lg shadow-lg w-80">
<div class="flex justify-between items-center bborder-b-2 border-b-grau2">
<div class="flex">
<h3 class="text-lg font-bold">{{ contact.display_name }}</h3>
<a class="px-2">@{{ contact.username }}</a>
</div>
<button @click="closeChat"><img src="../../assets/icons/x-klein.png" alt=""></button>
</div>
<div class="mt-2">
<ul class="space-y-2">
<li v-for="(msg) in geladeneNachrichten" :key="msg.msg_id || msg.id">
<div class="space-x-2">
<a class="font-bold">{{ msg.display_name }}</a>
</div>
<div>
<p>{{ msg.message }}</p>
</div>
</li>
</ul>
<div>
<form class="flex mt-4" @submit="sendMessage">
<label><input v-model="send_message" class="bg-schwarz outline-none border-b-2 mt-2 py-2 px-1" placeholder="Message"></label>
<button type="submit"><img src="../../assets/icons/send.png" alt="senden" class="ml-4 mt-1"></button>
</form>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,16 +0,0 @@
<script setup>
</script>
<template>
<div class="flex p-4 border-b-2 border-b-grau2 ">
<div class="w-12">
<img class="p-2 bg-grau rounded-l-lg" src="../../assets/icons/lupe.png" alt="">
</div>
<input type="text" class="bg-grau rounded-r-lg focus:outline-none text-weiss" placeholder="Search...">
</div>
</template>
<style scoped>
</style>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
// PLACEHOLDER
const hashtags = [ const hashtags = [
{id: 1,name: "xbox", nr_posts: 553,category: "Gaming" } , {id: 1,name: "xbox", nr_posts: 553,category: "Gaming" } ,
{id: 2,name: "lol" , nr_posts: 16422, category: "Gaming"}, {id: 2,name: "lol" , nr_posts: 16422, category: "Gaming"},

View File

@@ -1,21 +1,76 @@
<script setup> <script setup lang="ts">
import router from "../../router";
import { ref, onMounted } from 'vue';
let input_username_mail = ref("");
let input_user_password = ref("");
let rememberMe = ref(false);
onMounted(() => {
if (localStorage.getItem('username')) {
input_username_mail.value = localStorage.getItem('username') || "";
input_user_password.value = localStorage.getItem('password') || "";
rememberMe.value = false;
}
});
async function login(event: Event) {
event.preventDefault();
const username = input_username_mail;
const password = input_user_password;
if (username.value === "" || password.value === "") {
alert("Please fill all fields");
return;
}
try {
const response = await fetch('http://localhost:8000/api/account/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: username.value, password: password.value }),
});
if (response.status === 200) {
const data = await response.json();
localStorage.setItem('isLoggedIn', 'true');
localStorage.setItem('username', username.value);
localStorage.setItem('self_id', data["userId"]);
console.log("SELF LOG: " + data["userId"]);
alert("You will be now redirected");
router.push('/');
} else {
alert("Username/E-Mail or Password are wrong");
}
} catch (e) {
console.log("An error has occurred. Please try again later.");
console.log(e)
}
}
</script> </script>
<template> <template>
<div class="px-20 border-x border-x-grau2 pb-35p"> <div class="px-20 sm:border-x sm:border-x-grau2 pb-32">
<div class="text-3xl pt-32"> <div class="text-3xl pt-32"> <!-- ÜBERSCHRIFT-->
<p class="text-weiss text-center">Welcome to <span class="text-button-farbe">ESP</span>!</p> <p class="text-weiss text-center">Welcome to <label class="bg-schwarz p-1 rounded-lg mr-1"><span class="text-logo-farbe-lila">E</span><span class="text-logo-farbe-rot">S</span><span class="text-logo-farbe-blau">P</span></label>!</p>
<p class="text-weiss text-center">Login or create a new Account to continue</p> <p class="text-weiss text-center">Login to continue</p>
</div> </div>
<div class="px-20 pt-7"> <div class="px-20 pt-7"><!-- FORM --->
<form class="flex flex-col items-center"> <form class="flex flex-col items-center" @submit.prevent="login">
<input class="m-4 w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg" type="text" placeholder="Username or E-Mail"><br> <input class="m-4 md:w-full md:max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg" v-model="input_username_mail" type="text" placeholder="Username or E-Mail"><br>
<input class="m-4 w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg" type="text" placeholder="Password"><br> <input class="m-4 md:w-full md:max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg" v-model="input_user_password" type="password" placeholder="Password"><br>
<button class="m-4 bg-grau-dunkel w-full max-w-xs p-4 text-weiss rounded-lg">Login</button> <button class="m-4 bg-button-farbe w-full md:max-w-xs p-4 text-schwarz rounded-lg">Login</button>
</form> </form>
</div> </div>
<div>
<p class="text-weiss text-center pb-2 ">No Account? <router-link to="/register" class="text-button-farbe">Register here</router-link></p>
<p class="text-weiss text-center pt-2"> <router-link to="/wip" class="text-button-farbe">Forgot password?</router-link> </p>
</div>
</div> </div>
</template> </template>
<style scoped> <style scoped>

View File

@@ -0,0 +1,27 @@
<script setup>
import Contacts from "./home_components/contacts.vue";
import Feed from "./home_components/feed.vue";
import Legal from "./home_components/legal.vue";
import Navigationbar from "./home_components/navigationbar.vue";
import Msg_main from "./messages_components/msg_main.vue";
</script>
<template>
<div id="main" class=" sm:flex">
<div id="left" class="sm:w-72 min-w-72">
<navigationbar></navigationbar>
</div>
<div class="w-100p">
<msg_main></msg_main>
</div>
<div class="sm:w-1/4">
<contacts></contacts>
<legal></legal>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,60 @@
<script setup lang="ts">
import {ref} from "vue";
import Popup_chat from "../home_components/popup_chat.vue";
const nachrichten = ref([
{id: 1,
profile_picture: "../../assets/default_pp.png", display_name: "Linux Enjoyer",username: "lunix",
content:"Moin!",
type:"message",date: new Date(2024,11,13).toDateString() },
{id: 2,
profile_picture: "../../assets/default_pp.png",
display_name: "XBOX",username: "xbox",
content: "Hey, was geht?",
type:"message",date: new Date(2024,9,13).toDateString()},
{id: 4,// Sollte im feed sein
profile_picture: "../../assets/default_pp.png",
display_name: "klopupser13", username: "theopampa",
content: "ja ok können wir machen",
type:"following",date: new Date(2025,1,15).toDateString()}
]);
const selectedContact = ref(null);
const showChatPopup = ref(false);
function openChat(contact) {
selectedContact.value = contact;
showChatPopup.value = true;
}
</script>
<template>
<div class="border-x-grau2 border-x-2">
<div class="border-b-grau2 border-b-2">
<h1 class="text-weiss text-3xl p-4">Messages</h1>
</div>
<div>
<ul>
<li v-for="nachricht in nachrichten" :key="nachricht.id" class="border-b-grau2 border-b-2" @click="openChat(nachricht)">
<div class="flex p-4">
<img src="../../assets/default_pp.png" alt="user profile picture" class="rounded-full w-16 h-16">
<div>
<div class="flex mb-1">
<label class="text-lg font-bold sm:m-1 ml-1 text-weiss">{{nachricht.display_name}}</label>
<label class="text-lg sm:m-1 ml-1 text-grau2">@{{nachricht.username}}</label>
<label class="sm:m-2 ml-1 text-grau2">{{nachricht.date}}</label>
</div>
<a class="ml-1 text-weiss">{{nachricht.content}}</a>
</div>
</div>
</li>
</ul>
</div>
<popup_chat v-if="showChatPopup" :contact="selectedContact" @close="showChatPopup = false" />
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,26 @@
<script setup>
import Legal from "./home_components/legal.vue";
import Navigationbar from "./home_components/navigationbar.vue";
import Notifi_comp from "./notifications_components/notifi_main.vue";
import Contacts from "./home_components/contacts.vue";
</script>
<template>
<div class="text-weiss sm:flex">
<div id="left" class="border-1 border-b-grau sm:w-72 min-w-72">
<navigationbar></navigationbar>
</div>
<div class="w-100p border-x-2 border-x-grau2">
<notifi_comp></notifi_comp>
</div>
<div class="sm:w-1/4">
<contacts></contacts>
<legal></legal>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,124 @@
<script setup lang="ts">
console.log("SEITE WIRD AUSGEFÜHRT")
import {ref} from "vue";
const notifications = ref([
{id: 1, // Sollte im feed sein
profile_picture: "../../assets/default_pp.png",
author_display_name: "Linux Enjoyer",author_username: "lunix",
content:"I love Linux. My Favorite Linux Distro is ARCH LINUX.",
type:"message",date: new Date(2024,11,13).toDateString() }, // 11 = Dezember, 10 = Nov, 9 = okt., 8 = sep.
{id: 2,
profile_picture: "../../assets/default_pp.png",
author_display_name: "XBOX",author_username: "Xbox",
content: "Call of Duty: Black Ops 6 is OUT NOW. Check it out: https://xbox.com",
type:"message",date: new Date(2024,9,13).toDateString()},
{id: 33,// Sollte im feed sein
profile_picture: "../../assets/default_pp.png",
author_display_name: "ESP Support",author_username: "esp_support",
content: "You're post was against our TOS and got deleted. Additionally your account has been suspended for 30 Days." ,
type:"support",date: new Date(2024,9,13).toDateString()},
{id: 4,// Sollte im feed sein
profile_picture: "../../assets/default_pp.png",
author_display_name: "System", author_username: "esp_notifications",
content: "GitHub posted something new. Check it out",
type:"following",date: new Date(2024,10,2).toDateString()},
{id: 5, // Sollte im feed sein
profile_picture: "../../assets/danielvici_pp.png",
author_display_name: "danielvici123", author_username: "danielvici",
content: "I created this WebApp with VUE3 and TailwindCSS. It was a lot of fun.",
type:"message",date: new Date(2024,1,1).toDateString()}
]);
let feed = ref([]);
// wie enum in c bzw. arduino
interface sortting_definition {
f: boolean; // following
m: boolean; // messages
da: boolean; // date-asc
dd: boolean; // date-desc
u: boolean; // user
sorting: string;
}
// Standard filter und sorting
const check_type = ref<sortting_definition>({
f: true,
m: true,
da: false,
dd: false,
u: false,
sorting: "date-asc",
});
// Für jeden eintrag in notifications durschläuft die schleife einen durschlauf
for(let i =0; i < notifications.value.length; i++) {
console.log("Getting Notifications...");
feed.value.push(notifications.value[i]);
}
function go_filter(){
console.log("Filter applied");
console.log("These Filter applied: ", JSON.stringify(check_type.value));
feed.value = [];
notifications.value.forEach((notification) => {
if ((check_type.value.f == true && notification.type === "following") || (check_type.value.m == true && notification.type === "message")|| (notification.type === "support")){
feed.value.push(notification);
}
})
console.log("Feed:", feed.value);
}
</script>
<template>
<div>
<div class="border-b-grau2 border-b-2">
<h1 class="text-weiss text-3xl p-4">Notifications</h1>
</div>
<div class="text-grau2 p-5 border-b-2 "><!-- FILTER -->
<!-- Wenn das Form submited wurde wird die Seite nicht-->
<!-- neugeladen und die Funktion -->
<form @submit.prevent="go_filter">
<label class="p-2 text-xl text-weiss">Filter</label>
<label class="m-2 accent-logo-farbe-blau">You Following<input type="checkbox" class="m-1 mr-3" v-model="check_type.f"></label>
<label class="m-2 accent-logo-farbe-rot">Messages<input type="checkbox" class="m-1 mr-3" v-model="check_type.m"></label>
<!--<label class="m-2 accent-logo-farbe-rot">Other<input type="checkbox" class="m-1 mr-3" v-model="check_type.o"></label>-->
<label class="p2">Sort by <select name="sorting" id="notification_sorting" class="mr-3 bg-hintergrund-farbe text-weiss" v-model="check_type.sorting">
<option value="date-asc">Date (asc.)</option>
<option value="date-desc">Date (desc.)</option>
<option value="user">User</option>
</select></label>
<button type="submit" class="bg-button-farbe text-schwarz p-2 rounded-lg">Filter</button>
</form>
</div>
<div> <!-- MAIN NACHRICHTEN-->
<ul v-if="feed.length > 0">
<li v-for="(notification) in feed" :key="notifications.id" class="border-b-grau2 border-b-1 border-b p-3 flex">
<img src="../../assets/default_pp.png" alt="user profile picture" class="rounded-full w-16 h-16">
<div>
<div class="flex">
<label class="text-lg font-bold m-1 text-weiss">{{notification.author_display_name}}</label>
<label class="m-2 text-grau2">Type: {{notification.type}}</label>
<label class="m-2 text-grau2">Date: {{notification.date}}</label>
</div>
<a class="ml-1">{{notification.content}}</a>
</div>
</li>
</ul>
<a v-else>You have no Notifications!</a>
</div>
</div>
</template>
<style scoped>
</style>

327
src/components/posts.vue Normal file
View File

@@ -0,0 +1,327 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import {useRoute, useRouter} from 'vue-router';
import Navigationbar from './home_components/navigationbar.vue';
import Contacts from './home_components/contacts.vue';
import Legal from './home_components/legal.vue';
interface Post {
post_uuid: number;
user_id: number;
author_username: string;
post_text: string;
likes: number;
comments_count: number;
comments: any[];
created_at: string;
}
interface User {
user_id: number;
username: string;
displayname: string;
}
interface Comment {
comment_id: number;
author_user_id: number;
post_id: number;
text: string;
likes: number;
created_at: string;
displayname: string;
username: string;
}
const route = useRoute();
const router = useRouter();
const post = ref<Post | null>(null);
const user = ref<User | null>(null);
const comments = ref<Comment[] | null>(null);
let comment_text = ref();
let self_id;
const loading = ref(true);
let post_uuid = ref();
async function getPost(post_id: any) {
try {
const post_response = await fetch(`http://localhost:8000/api/post/${post_id}`, {method: 'GET'});
const fetchedPost: Post = await post_response.json();
if (post_response.status === 404) {
console.error("No comments found.");
alert("Post not found")
await router.push('/');
return;
}
const user_response = await fetch('http://localhost:8000/api/users', {method: 'GET'});
const usersDATA = await user_response.json();
const postAuthor = usersDATA.find(user => user.user_id === fetchedPost.user_id);
if (postAuthor) {
console.log("The post was written by:", postAuthor);
} else {
console.error("Author not found.");
}
user.value = postAuthor;
post.value = fetchedPost;
console.log(post.value);
} catch (e) {
console.error(e);
} finally {
loading.value = false;
}
}
async function getComment(post_id: any) {
try {
const comments_response = await fetch(`http://localhost:8000/api/post/${post_id}/comments`, { method: 'GET' });
const fetchedComments: Comment[] = await comments_response.json();
const user_response = await fetch(`http://localhost:8000/api/users`, { method: 'GET' });
const usersDATA: User[] = await user_response.json();
if(comments_response.status === 404 || user_response.status === 404) {
console.error("ERRROR");
alert("Error try it again later.");
await router.push('/');
return;
}
comments.value = fetchedComments.map(comment => {
const author = usersDATA.find(u => u.user_id === comment.author_user_id) || {
username: 'Unknown',
displayname: 'Unknown',
};
return {
...comment,
username: author.username,
displayname: author.displayname,
};
});
comments.value.sort((a, b) => b.post_id - a.post_id);
console.log(comments.value);
} catch (e) {
console.error(e);
}
}
function copyLink(post_id: string | number) {
const tocopy = `http://localhost:5173/post/${post_id}`;
navigator.clipboard.writeText(tocopy);
alert("Copied to clipboard");
}
async function addLike() { // Post liken
try {
post.value.likes++;
const response_like = await fetch(`http://localhost:8000/api/post/${post_uuid.value}/like`, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: `{"userId":${self_id}}`,
});
if(response_like.status === 404) {
console.error("ERROR");
await router.push('/');
return;
}
const data = await response_like.json();
console.log("post_id 2: ", post_uuid);
return data;
} catch (error) {
console.error('Fehler beim Liken des Posts:', error);
throw error;
}
}
async function comment_create_text(comment_text: string) {
if (comment_text === null) {
alert("Please write something before commenting.");
return;
}
try {
const response = await fetch(`http://localhost:8000/api/post/${post_uuid.value}/comment`, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: `{"userId":${self_id},"text":"${comment_text}"}`,});
const data = await response.json();
if(response.status === 404) {
console.error("ERROR");
await router.push('/');
return;
}
await getComment(parseInt(post_uuid.value));
return data;
} catch (error) {
console.error(error);
}
console.log(comment_text);
}
async function addLike_comment(comment_id: number | string) {
try {
post.value.likes++;
const response_like = await fetch(`http://localhost:8000/api/comment/${comment_id}/like`, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: `{"userId":${self_id}}`,
});
if(response_like.status === 404) {
console.error("ERROR");
await router.push('/');
return;
}
const data = await response_like.json();
console.log("post_id: ", comment_id);
console.log(data);
getComment(parseInt(post_uuid.value));
return data;
} catch (error) {
console.error('Fehler beim Liken des Posts:', error);
throw error;
}
}
function gotoProfile(user_id: string | number) {
router.push(`/profile/${user_id}`);
}
onMounted(async () => {
console.log("PARAMS: "+ route.path);
const pathArray = route.path.split('/');
console.log(pathArray);
if (pathArray.length > 2) {
post_uuid.value = pathArray[2];
console.log("post_id 0: ", post_uuid.value);
}
if (!post_uuid) {
alert('No post selected. Redirecting to feed.');
await router.push('/');
return;
}
self_id = localStorage.getItem('self_id');
getPost(parseInt(post_uuid.value));
getComment(parseInt(post_uuid.value));
});
</script>
<template>
<div id="main" class="bg-hintergrund-farbe sm:flex overflow-y-auto sm:h-full sm:scrollbar">
<div id="left" class="sm:w-72 min-w-72">
<navigationbar></navigationbar>
</div>
<div class="text-weiss sm:w-100p w-screen sm:border-x sm:border-x-grau2" v-if="post">
<div> <!-- HEADER -->
<h2 class="align-middle p-6 text-3xl text-weiss border-b-grau2 border-b">Post</h2>
</div>
<div class="flex px-2 py-4 border-b-grau2 border-b">
<img v-if="user.username != 'danielvici' " src="../assets/default_pp.png" alt="" class="rounded-full w-16 h-16">
<img v-else src="../assets/danielvici_pp.png" alt="" class="rounded-full w-16 h-16">
<div>
<div> <!-- POST HEADER -->
<label class="text-lg font-bold m-1 text-weiss">{{user.displayname}}</label>
<label class="text-base m-1 text-grau2">@{{ user.username }} | </label>
<label class="text-base text-grau2"> Posted at {{ post.created_at }}</label>
</div>
<div class="m-2"> <!-- POST CONTENT -->
<p class="text-sm m-1 text-weiss">{{ post.post_text }}</p>
</div>
<div class="flex"> <!-- POST FOOTER -->
<div class="flex"> <!-- Comments -->
<img src="../assets/icons/comment.png" alt="" class="rounded-full align-middle">
<label class="text-sm m-1 text-weiss" v-if="post.comments != undefined">{{ post.comments }}</label>
<label class="text-sm m-1 text-weiss" v-else>Comments disabled</label>
</div>
<div class="flex items-center" @click="addLike()"> <!-- Likes -->
<img alt="" src="../assets/icons/herz.png" class="align-middle">
<label class="text-sm m-1 text-weiss">{{ post.likes }}</label>
</div>
<button @click="copyLink(post_uuid)" class="text-schwarz pl-1 mx-1 px-1 rounded-lg bg-button-farbe">Share Post</button>
</div>
</div>
</div>
<div> <!-- COMMENTS VIEW and WRITE -->
<div>
<h2 class="align-middle p-6 text-xl text-weiss border-b-grau2 border-b">Comments</h2>
</div>
<div> <!-- WRITE COMMENTS -->
<div class="flex border-b-2 border-b-grau2">
<img v-if="self_id != '99' " src="../assets/default_pp.png" alt="" class="p-2 rounded-full w-16 h-16">
<img v-else src="../assets/danielvici_pp.png" alt="" class="p-2 rounded-full w-16 h-16">
<!-- <img src="../assets/danielvici_pp.png" alt="" class="p-2 rounded-full w-16 h-16">-->
<form class="w-full pr-5">
<!-- post_publish ist richtig aber wird falsch angezeigt. File Input geht nicht-->
<textarea v-model="comment_text" name="post_text" class="bg-hintergrund-farbe rounded-lg m-2 p-1 focus:outline-none text-grau2 w-full resize-none" rows="3" placeholder="Write something..."></textarea>
<div class="">
<button id="post_publish" name="post_publishss" class="text-weiss p-1 m-2 rounded-lg py-3 px-5 bg-button-farbe" @click.prevent="comment_create_text(comment_text)" type="button">Post</button>
</div>
</form>
</div>
</div>
<div> <!-- VIEW COMMENTS -->
<div class="sm:overflow-y-scroll h-[450px]"> <!-- VIEW COMMENTS -->
<ul v-if="comments && comments.length > 0">
<li v-for="c in comments" :key="c.comment_id" class="p-4 border-b border-gray-700">
<div class="flex">
<img v-if="c.author_user_id != '99' " src="../assets/default_pp.png" alt="" class="p-2 rounded-full w-16 h-16">
<img v-else src="../assets/danielvici_pp.png" alt="" class="p-2 rounded-full w-16 h-16">
<div>
<div> <!-- POST HEADER -->
<label class="text-lg font-bold m-1 text-weiss">{{c.displayname}}</label>
<label class="text-base m-1 text-grau2">@{{ c.username }}</label>
</div>
<div class="m-2"> <!-- POST CONTENT -->
<p class="text-sm m-1 text-weiss">{{ c.text }}</p>
</div>
<div class="flex"> <!-- POST FOOTER -->
<div class="flex items-center" @click="addLike_comment(c.comment_id)"> <!-- Likes -->
<img alt="" src="../assets/icons/herz.png" class="align-middle">
<label class="text-sm m-1 text-weiss">{{ c.likes }}</label>
</div>
</div>
</div>
</div>
</li>
</ul>
<div v-else class="p-4 text-gray-400">No comments yet.</div>
</div>
</div>
</div>
</div>
<div class="sm:w-1/4 w-screen">
<contacts></contacts>
<legal></legal>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,26 @@
<script setup>
import Navigationbar from "./home_components/navigationbar.vue";
import Contacts from "./home_components/contacts.vue";
import Legal from "./home_components/legal.vue";
import Profile_main from "./profile_components/profile_main.vue";
</script>
<template>
<div id="main" class="bg-hintergrund-farbe sm:flex overflow-visible scrollbar sm:overflow-y-auto sm:h-full sm:scrollbar">
<div id="left" class="sm:w-72 min-w-72">
<navigationbar></navigationbar>
</div>
<div class="sm:w-100p w-screen sm:border-x sm:border-x-grau2">
<profile_main></profile_main>
</div>
<div id="right" class="sm:w-1/4 w-screen">
<contacts></contacts>
<legal></legal>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,205 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const upc = ref([]);
const profile_id = ref<number | null>(null);
const userData = ref<any>(null);
onMounted(async () => {
const pathArray = route.path.split('/');
if (pathArray.length > 2) {
profile_id.value = parseInt(pathArray[2], 10);
} else {
console.warn("No profile ID found in the route.");
}
if (!profile_id.value) {
alert('No profile selected. Redirecting to feed.');
await router.push('/');
return;
}
await create_own_posts();
await getUser();
});
async function create_own_posts() {
try {
const post_response = await fetch('http://localhost:8000/api/posts', {
method: 'GET',
});
if (!post_response.ok) {
throw new Error(`HTTP error! status: ${post_response.status}`);
}
const postsDATA = await post_response.json();
upc.value = postsDATA.filter((post) => post.user_id === profile_id.value);
if (upc.value.length === 0) {
console.warn('No posts found for this user.');
return;
}
//console.log("upc: "+ JSON.stringify(upc.value, null, 2));
return upc.value;
} catch (error) {
console.error('Error fetching posts:', error);
upc.value = [];
}
}
async function addLike(post_id: string | number, user_id: number, index: number) {
try {
upc.value[index].likes++;
const response = await fetch(`http://localhost:8000/api/post/${post_id}/like`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: `{"userId":${user_id}}`,
});
if (!response.ok) {
const errorText = await response.text();
console.error('Server-Fehlertext:', errorText);
throw new Error(`HTTP error! status: ${response.status}, text: ${errorText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fehler beim Liken des Posts:', error);
throw error;
}
}
function consoleLog() {
console.log("upc: ", upc.value);
console.log("profile_id: ", profile_id.value);
}
function gotoPost(post_id: string | number) {
localStorage.setItem("viewedpost", post_id.toString());
router.push(`/post/${post_id}`);
}
function copyLink(post_id: string | number) {
const tocopy = `http://localhost:5173/post/${post_id}`;
navigator.clipboard.writeText(tocopy);
alert("Copied to clipboard with");
}
async function getUser() {
try {
const response = await fetch('http://localhost:8000/api/users/');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json();
const user = users.find((u) => u.user_id === profile_id.value);
if (user) {
const followerCount = JSON.parse(user.followers).length;
const followingCount = JSON.parse(user.following).length;
userData.value = {
...user,
followerCount,
followingCount,
};
console.log("userData: ", userData.value);
return userData;
} else {
console.error('Benutzer nicht gefunden.');
userData.value = null;
}
} catch (error) {
console.error('Fehler beim Abrufen der Benutzerdaten:', error);
userData.value = null;
}
}
function copyUser(){
const tocopy = `http://localhost:5173/profile/${profile_id.value}`;
navigator.clipboard.writeText(tocopy);
alert("Copied to clipboard");
}
</script>
<template>
<div>
<h2 class="align-middle p-6 text-3xl text-weiss border-b-grau2 border-b ">Profile</h2>
<div class="mb-6" v-if="userData">
<div class="text-weiss p-2 flex justify-center">
<img v-if="userData.user_id != '99' " src="../../assets/default_pp.png" alt="" class="rounded-full size-36">
<img v-else src="../../assets/danielvici_pp.png" alt="" class="rounded-full size-36">
</div>
<div class="text-center p-2 flex flex-col">
<label class="text-xl font-bold m-1 text-weiss" @click="consoleLog()">{{ userData.displayname }}</label>
<label class="text-base m-1 text-grau2">@{{ userData.username }}</label>
</div>
<div class="text-center py-4">
<label class="text-base m-1 text-grau2">Bio:</label>
<p class="text-sm m-1 text-weiss italic">"{{ userData.bio }}"</p>
</div>
<div class="text-grau2 p2 text-center">
<label class="text-base m-1 p-2"> Followers <a class="text-weiss">{{ userData.followerCount }}</a></label>
<label class="text-base m-1 p-2"> Following <a class="text-weiss">{{ userData.followingCount }}</a></label>
</div>
<div class="flex justify-center pt-5">
<button @click="copyUser" class="text-schwarz mx-1 px-1 rounded-lg bg-button-farbe">Share Profile</button>
</div>
</div>
<div v-else class="flex-col justify-center p-5 text-center">
<a class="text-weiss text-3xl"> USER NOT FOUND</a> <br>
<router-link to="/" class="text-button-farbe text-3xl text-center"> Go to Feed</router-link>
</div>
<div>
<h2 class="align-middle p-6 text-xl text-weiss border-y-grau2 border-y ">Posts</h2>
</div>
<div class="sm:overflow-y-auto sm:h-[400px] sm:scrollbar">
<ul v-if="upc.length > 0">
<li v-for="(postitem, indexus) in upc" :key="postitem.user_id" class="border border-grau2 p-3 flex">
<img v-if="postitem.user_id != '99' " src="../../assets/default_pp.png" alt="" class="rounded-full w-16 h-16">
<img v-else src="../../assets/danielvici_pp.png" alt="" class="rounded-full w-16 h-16">
<div>
<div>
<label class="text-lg font-bold m-1 text-weiss">{{ userData.displayname }}</label>
<label class="text-base m-1 text-grau2">@{{ userData.username }}</label>
</div>
<div class="m-2">
<p class="text-sm m-1 text-weiss">{{ postitem.post_text }}</p>
</div>
<div class="sm:flex">
<div class="flex items-center">
<div class="flex">
<img src="../../assets/icons/comment.png" alt="" class="rounded-full align-middle">
<label class="text-sm m-1 text-weiss" v-if="postitem.comments != undefined">{{ postitem.comments }}</label>
<label class="text-sm m-1 text-weiss" v-else>Comments disabled or no comments</label>
</div>
<div class="flex items-center" @click="addLike(postitem.post_id, postitem.user_id, indexus)">
<img alt="" src="../../assets/icons/herz.png" class="align-middle">
<label class="text-sm m-1 text-weiss">{{ postitem.likes }}</label>
</div>
</div>
<br class="sm:hidden">
<div class="flex sm:items-center mx-2">
<button @click="gotoPost(postitem.posts_uuid)" class="text-schwarz mx-1 px-1 rounded-lg bg-button-farbe">View Post</button>
<button @click="copyLink(postitem.posts_uuid)" class="text-schwarz pl-1 mx-1 px-1 rounded-lg bg-button-farbe">Share Post</button>
</div>
</div>
</div>
</li>
</ul>
<p v-else class="text-weiss text-center justify-center text-lg pt-5"> This user has not posted anything yet. </p>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,25 @@
<script setup>
import Legal from "./home_components/legal.vue";
import Register_main from "./register_components/register_main.vue";
import router from "../router";
function route_home() {
router.push("/");
}
</script>
<template>
<div id="main" class="bg-hintergrund-farbe sm:flex p-2 sm:justify-between overflow-auto h-screen scrollbar">
<div class="pt-5">
<img src="../assets/esp-logo_no_text.png" alt="" class="rounded-lg h-12 w-24 hover:shadow-2xl hover:shadow-grau-dunkel" @click="route_home">
</div>
<register_main class="sm:inset-0"></register_main>
<div class="sm:inset-y-0 sm:right-0 sw:max-w-36 text-center">
<legal></legal>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,78 @@
<script setup lang="ts">
import {ref} from "vue";
import router from "../../router";
let register_input_username = ref("");
let register_input_displayname = ref("");
let register_input_password = ref("");
let register_input_email = ref("");
async function register() {
const username = register_input_username;
const displayname = register_input_displayname;
const email = register_input_email;
const password = register_input_password;
const std_text = "default";
if (username.value === "" || password.value === "" || displayname.value === "" || email.value === "") {
alert("Please fill all fields");
return;
}
console.log("Username: " + username.value + ", Password: " + password.value);
try {
const response = await fetch('http://localhost:8000/api/account/register', {
method: 'POST',
headers: {
'Content-Type': 'login/json',
},
body: JSON.stringify({username: username.value, password: password.value, userGroup: "user", displayname: displayname.value, user_email: email.value, firstname: std_text, surname: std_text}),
});
if (response["status"] == 200) {
const data = await response.json();
localStorage.setItem('isLoggedIn', 'true');
localStorage.setItem('username', username.value);
localStorage.setItem('self_id', data["userId"]);
console.log("SELF REG: " + data["userId"]);
alert("Account created! You will be now redirected");
router.push('/');
router.go(1);
}
const data = await response.json();
console.log(response);
} catch (e) {
console.log("An error has occurred. Please try again later.");
console.error(e);
}
}
</script>
<template>
<div id="main" class="bg-hintergrund-farbe p-2 sm:border-x sm:border-x-grau2 sm:px-20">
<div class="text-3xl pt-20px sm:pt-32"> <!-- ÜBERSCHRIFT-->
<p class="text-weiss text-center">Welcome to <label class="bg-schwarz p-1 rounded-lg mr-1"><span class="text-logo-farbe-lila">E</span><span class="text-logo-farbe-rot">S</span><span class="text-logo-farbe-blau">P</span></label>!</p>
<p class="text-weiss text-center pt-2">Join today!</p>
</div>
<div class="px-20 pt-7"> <!-- FORM --->
<form class="flex flex-col items-center" @submit.prevent="register">
<input class="m-4 sm:w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg" v-model="register_input_username" type="text" placeholder="Username" required><br>
<input class="m-4 sm:w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg" v-model="register_input_displayname" type="text" placeholder="Displayname" required><br>
<input class="m-4 sm:w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg" v-model="register_input_email" type="email" placeholder="E-Mail" required><br>
<input class="m-4 sm:w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg" v-model="register_input_password " type="password" placeholder="Password" required><br>
<button class="m-4 bg-button-farbe sm:w-full max-w-xs p-4 text-schwarz rounded-lg">Create Account</button>
</form>
<p class="text-weiss w-100p text-center">Already have an account? <router-link to="/login" class="text-button-farbe">Login</router-link></p>
</div>
</div>
</template>
<style scoped>
</style>

30
src/components/search.vue Normal file
View File

@@ -0,0 +1,30 @@
<script setup>
import Navigationbar from "./home_components/navigationbar.vue";
import Legal from "./home_components/legal.vue";
import Search_main from "./search_components/search_main.vue";
import Contacts from "./home_components/contacts.vue";
</script>
<template>
<div class="text-weiss sm:flex">
<div id="left" class="sm:w-72 sm:min-w-72 border-1 border-b-grau">
<navigationbar></navigationbar>
</div>
<div class="sm:w-100p w-screen border-x-2 border-x-grau2">
<div class="border-b-grau2 border-b-2">
<h1 class="text-weiss text-3xl p-4">Search</h1>
</div>
<search_main></search_main>
</div>
<div class="sm:w-1/4">
<contacts></contacts>
<legal></legal>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,221 @@
<script setup lang="ts">
import {ref, onMounted} from "vue";
import router from "../../router";
const allItems = ref<any[]>([]);
const feed = ref<any[]>([]);
const feed2 = ref<any[]>([]);
interface search_filter {
u: boolean;
p: boolean;
}
const search_filter_status = ref<search_filter>({
u: true,
p: true,
});
let searched = false;
const usr_search = ref<string | undefined>(undefined);
async function get_posts_user() {
try {
const post_response = await fetch('http://localhost:8000/api/posts');
const postsDATA = await post_response.json();
const user_response = await fetch('http://localhost:8000/api/users');
const usersDATA = await user_response.json();
usersDATA.forEach(user => {
allItems.value.push({...user, id: user.user_id, type: 'user'});
});
postsDATA.forEach(post => {
const author = usersDATA.find(user => user.user_id === post.user_id);
const postWithAuthorInfo = {
...post,
type: 'post',
username: author ? author.username : 'unknown',
displayname: author ? author.displayname : 'unknown',
};
allItems.value.push(postWithAuthorInfo);
});
feed.value = [...allItems.value];
feed2.value = [...allItems.value];
//console.log("Data Loaded:", allItems.value);
} catch (error) {
console.error("Error:", error);
}
}
onMounted(async () => {
get_posts_user();
});
function go_fs(){
searched = true;
feed.value = [];
feed2.value = [];
const combinedFilteredItems = allItems.value
.filter(item => (search_filter_status.value.u == true && item.type === "user") ||
(search_filter_status.value.p == true && item.type === "post"))
.map(item => {
if (item.type === 'post') {
const user = allItems.value.find(u => u.id === item.user_id && u.type === 'user');
return {
...item,
username: user ? user.username : 'unknown',
displayname: user ? user.displayname : 'unknown',
};
}
return item;
});
feed2.value = combinedFilteredItems;
const searchTerm = usr_search.value ? usr_search.value.toLowerCase() : '';
if (!searchTerm) {
feed.value = [...feed2.value];
return;
}
feed.value = feed2.value.filter(item => {
switch (item.type) {
case "user":
return (item.displayname && item.displayname.toLowerCase().includes(searchTerm)) || (item.username && item.username.toLowerCase().includes(searchTerm));
case "post":
return (item.post_text && item.post_text.toLowerCase().includes(searchTerm)) || (item.username && item.username.toLowerCase().includes(searchTerm) || item.displayname && item.displayname.toLowerCase().includes(searchTerm));
default:
return false;
}
});
}
async function addLike(post_id: string | number, user_id: number, index: number) {
try {
feed.value[index].likes++;
const response = await fetch(`http://localhost:8000/api/post/${post_id}/like`, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: `{"userId":${user_id}}`,
});
if (!response.ok) {
const errorText = await response.text();
console.error('Server-Fehlertext:', errorText);
throw new Error(`HTTP error! status: ${response.status}, text: ${errorText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fehler beim Liken des Posts:', error);
throw error;
}
}
function gotoPost(post_id: string ) {
localStorage.setItem("viewedpost", post_id);
router.push(`/post/${post_id}`);
console.log("goto post: " + post_id);
}
function copyLink(post_id: string | number) {
const tocopy = `http://localhost:5173/post/${post_id}`;
navigator.clipboard.writeText(tocopy);
alert("Copied to clipboard");
}
function gotoProfile(user_id: string | number) {
router.push(`/profile/${user_id}`);
}
</script>
<template>
<div>
<div class="flex justify-center">
<div class="w-1/2">
<form @submit.prevent="go_fs" class="flex flex-col">
<input type="text" placeholder="Search..." class="w-full m-2 mt-6 p-4 bg-grau-dunkel focus:outline-none rounded-xl placeholder:text-center text-center" v-model="usr_search">
<div class="flex justify-center text-grau2">
<label class="m-2 accent-button-farbe"><input type="checkbox" class="mr-1" v-model="search_filter_status.u">User</label>
<label class="m-2 accent-button-farbe"><input type="checkbox" class="mr-1" v-model="search_filter_status.p">Post</label>
</div>
<button class="text-schwarz pl-1 mx-1 px-1 rounded-lg bg-button-farbe w-1/2 place-self-center">Filter /Search </button>
</form>
</div>
</div>
<div>
<div>
<a class="text-2xl flex justify-center mt-4 pt-2 p-3 border-b-grau2 border-b">Result(s):</a>
</div>
<div v-if="feed.length > 0 && searched == true" class="sm:overflow-y-auto sm:h-[650px] sm:scrollbar">
<div v-for="(bing, i) in feed" :key="bing.id">
<div v-if="bing.type === 'user'" class="pt-2 p-3 border-b-grau2 border-b">
<div class="flex">
<img v-if="bing.user_id != '99' " src="../../assets/default_pp.png" alt="" class="rounded-full w-16 h-16">
<img v-else src="../../assets/danielvici_pp.png" alt="" class="rounded-full w-16 h-16">
<div class="">
<a class="text-lg m-1">{{ bing.displayname }}</a>
<a class="text-base m-1 text-grau2">@{{ bing.username }}</a>
<div>
<a class=" text-sm m-3 text-weiss" v-if="bing.bio">{{ bing.bio }}</a>
<a class=" text-sm m-3 text-grau2 italic" v-else>User has no bio.</a>
</div>
<button @click="gotoProfile(bing.user_id)" class="text-schwarz pl-1 mx-1 px-1 rounded-lg bg-button-farbe"> Go to Profile</button>
</div>
</div>
</div>
<div v-else-if="bing.type === 'post'" class="flex p-3 border-b-grau2 border-b "> <!-- POSTS -->
<img v-if="bing.user_id != '99' " src="../../assets/default_pp.png" alt="" class="rounded-full w-16 h-16">
<img v-else src="../../assets/danielvici_pp.png" alt="" class="rounded-full w-16 h-16">
<div>
<div>
<label class="text-lg m-1 text-weiss">{{ bing.displayname }}</label>
<label class="text-base m-1 text-grau2">@{{ bing.username }}</label>
</div>
<div class="m-2">
<p class="text-sm m-1 text-weiss">{{ bing.post_text }}</p>
</div>
<div class="sm:flex pt-2">
<div class="flex">
<div class="flex">
<img src="../../assets/icons/comment.png" alt="" class="rounded-full align-middle">
<label class="text-sm m-1 text-weiss" v-if="bing.comments != undefined">{{ bing.comments }}</label>
<label class="text-sm m-1 text-weiss" v-else>Comments disabled</label>
</div>
<div class="flex items-center" @click="addLike(bing.posts_uuid, bing.user_id, i)">
<img alt="" src="../../assets/icons/herz.png" class="align-middle">
<label class="text-sm m-1 text-weiss">{{ bing.likes }}</label>
</div>
</div>
<br class="sm:hidden">
<div class="flex sm:tems-center mx-2"> <!-- View Post -->
<button @click="gotoPost(bing.posts_uuid)" class="text-schwarz mx-1 px-1 rounded-lg bg-button-farbe">View Post</button>
<button @click="copyLink(bing.posts_uuid)" class="text-schwarz pl-1 mx-1 px-1 rounded-lg bg-button-farbe">Share Post</button>
<button @click="gotoProfile(bing.user_id)" class="text-schwarz pl-1 mx-1 px-1 rounded-lg bg-button-farbe"> Go to Profile</button>
</div><!-- ENDE -->
</div>
</div>
</div>
</div>
</div>
<div v-else-if="searched == false" class="flex justify-center">
<a class="text-2xl text-logo-farbe-rot p-10 text-center">To see results you have to search</a>
</div>
<div v-else class="flex justify-center">
<a class="text-xl text-logo-farbe-rot p-10 text-center">No Results Found</a>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import { ref } from 'vue';
import Legal from "./home_components/legal.vue";
import Settings_sidebar from "./settings_components/settings_sidebar.vue";
import Settings_main from "./settings_components/settings_main.vue";
import Navigationbar from "./home_components/navigationbar.vue";
import Contacts from "./home_components/contacts.vue";
const selectedSetting = ref('');
// Aktualisieren der ausgewählten Einstellung
function handleUpdateSetting(setting: string) {
selectedSetting.value = setting;
console.log(`Setting got (S): ${setting}`);
}
</script>
<template>
<div id="main" class="bg-hintergrund-farbe sm:flex">
<div id="left" class="sm:w-72 min-w-72">
<navigationbar></navigationbar>
<settings_sidebar @updateSetting="handleUpdateSetting"></settings_sidebar>
</div>
<div class="w-100p sm:border-x-grau2 sm:border-x-2 my-2">
<settings_main :selectedSetting="selectedSetting"></settings_main>
</div>
<div class="sm:w-1/4">
<contacts></contacts>
<legal></legal>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,82 @@
<script setup>
import { ref, onMounted } from "vue";
let self = ref(null); // Initialisiere self mit null
let self_id = ref();
async function getSelf() {
try {
const response = await fetch('http://localhost:8000/api/users', { method: 'GET' });
if (!response.ok) {
console.error(`HTTP-Fehler! Status: ${response.status}`);
self.value = null;
return;
}
const users = await response.json();
const foundUser = users.find(user => {
return String(user.user_id) === self_id.value;
});
if (foundUser) {
self.value = foundUser;
} else {
self.value = null;
}
} catch (error) {
console.error('ERROR:', error);
self.value = null;
}
}
onMounted(() => {
self_id.value = localStorage.getItem('self_id');
getSelf();
console.log("SELF ID (onMounted):", self_id.value);
});
</script>
<template>
<div class="space-y-2 pl-2" v-if="self">
<p class="text-2xl text-weiss pt-2 items-center flex justify-center">{{ self.displayname }}</p>
<div>
<a class="text-lg text-weiss">Username</a><br>
<a class="text-grau2">{{ self.username }}</a>
</div>
<div>
<a class="text-lg text-weiss">User created at</a><br>
<a class="text-grau2">{{ self.account_created }}</a>
</div>
<div>
<a class="text-lg text-weiss">Email</a><br>
<a class="text-grau2">{{ self.user_email }}</a>
</div>
<div>
<a class="text-lg text-weiss">User ID</a><br>
<a class="text-grau2">{{ self.user_id }}</a>
</div>
<div>
<a class="text-lg text-weiss">Firstname</a><br>
<a class="text-grau2">{{ self.firstname }}</a>
</div>
<div>
<a class="text-lg text-weiss">Surname</a><br>
<a class="text-grau2">{{ self.surname }}</a>
</div>
<div>
<a class="text-lg text-weiss">Bio</a><br>
<a class="text-grau2">{{ self.bio }}</a>
</div>
</div>
<div v-else class="flex justify-center items-center">
<p class="text-2xl text-weiss pt-2 items-center flex justify-center">User data is being loaded or does not exist</p>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import router from "../../router";
function deleteAccount() {
// alert("Account deleted. You will be redirected to the login page.");
router.push("/login");
}
function handleSubmit(event: Event) {
event.preventDefault();
deleteAccount();
}
</script>
<template>
<div class="flex-col flex items-center justify-center">
<div> <!-- HEADER -->
<h1 class="text-weiß text-2xl p-4">Delete Account</h1>
</div>
<div>
<form class="flex flex-col space-y-4 p-4" @submit.prevent="handleSubmit">
<div class="flex flex-col space-y-2 items-center justify-center">
<input type="password" name="password" id="password" placeholder="Password" required class="m-2 w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg">
</div>
<div>
<input type="checkbox" name="verification" id="verification" required class="">
<label for="verification" class="p-4">I agree that my account will be deleted permanently.</label>
</div>
<button type="submit" class="bg-logo-farbe-rot text-schwarz text-xl font-bold p-4 mt-8 rounded-lg">Delete Account</button>
</form>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,39 @@
<script setup lang="ts">
import router from "../../router";
function reset_password() {
// alert("Account deleted. You will be redirected to the login page.");
router.push("/");
}
function handleSubmit(event: Event) {
event.preventDefault();
reset_password();
}
</script>
<template>
<div>
<div> <!--HEADER-->
<h1 class="text-weiss text-2xl p-3 items-center flex justify-center">Reset Password</h1>
</div>
<div>
<form class="flex flex-col space-y-4 p-4" @submit.prevent="handleSubmit">
<div class="flex flex-col space-y-2">
<input type="password" name="old-password" id="old-password" placeholder="Old Password" class="m-2 w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg invalid:border-logo-farbe-rot invalid:border-2">
</div>
<div class="flex flex-col space-y-2">
<input type="password" name="new-password" id="new-password" placeholder="New Passoword" class="m-2 w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg invalid:border-logo-farbe-rot invalid:border-2">
</div>
<div class="flex flex-col space-y-2">
<input type="password" name="new-password-repeat" id="new-password-repeat" placeholder=" Repeat new passoword" class="mt-4 m-2 w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg invalid:border-logo-farbe-rot invalid:border-2">
</div>
<button type="submit" class="bg-button-farbe text-schwarz text-xl font-bold p-4 mt-8 rounded-lg">Change Password</button>
</form>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import {defineEmits, ref} from 'vue';
const emit = defineEmits(['updateAccountSetting']);
const selectedSetting = ref('');
function updateAccountSetting(setting: string) {
emit('updateAccountSetting', setting);
selectedSetting.value = setting;
console.log(`Setting selected (SA): ${setting}`);
}
</script>
<template>
<div class=""> <!-- MAIN DIV -->
<div class="flex flex-col text-xl pl-4 pt-5 pr-4 space-y-5 text-grau2"> <!-- BODY -->
<label @click="updateAccountSetting('sa_account-information')" :class="{'text-weiss': selectedSetting === 'sa_account-information' || selectedSetting === ''}">Account Information</label>
<label @click="updateAccountSetting('sa_reset-password')" :class="{'text-weiss': selectedSetting === 'sa_reset-password'}">Reset Password</label>
<label @click="updateAccountSetting('sa_delete-account')" :class="{'text-weiss': selectedSetting === 'sa_delete-account'}">Delete Account</label>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
import {defineProps, ref} from 'vue';
import Sa_accountInformation_comp from "./sa_account-information_comp.vue";
import Sa_reset_password from "./sa_reset_password.vue";
import Sa_delete_account from "./sa_delete_account.vue";
const props2 = defineProps({
selectedAccountSetting: String
});
console.log(`Setting got (SAM): ${props2.selectedAccountSetting}`);
</script>
<template>
<div class="border-l-2 border-l-grau2"> <!-- MAIN DIV -->
<div v-if="props2.selectedAccountSetting === 'sa_account-information'">
<sa_account-information_comp></sa_account-information_comp>
</div>
<div v-else-if="props2.selectedAccountSetting === 'sa_reset-password'">
<sa_reset_password></sa_reset_password>
</div>
<div v-else-if="props2.selectedAccountSetting === 'sa_delete-account'">
<sa_delete_account></sa_delete_account>
</div>
<div v-else>
<sa_account-information_comp></sa_account-information_comp>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
import { ref } from 'vue';
import Settings_account from "./settings_account.vue";
import Settings_account_main from "./settings_account_main.vue";
import Sps_main from "./sps_main.vue";
import So_main from "./so_main.vue";
const props = defineProps({
selectedSetting: String
});
const selectedAccountSetting = ref('');
function handleUpdateAccountSetting(setting: string) {
selectedAccountSetting.value = setting;
}
console.log(`Setting got (SM): ${props.selectedSetting}`);
</script>
<template>
<div class="text-weiss">
<div v-if="props.selectedSetting === 'setting_account'"> <!-- ACCOUNT SETTINGS-->
<div class="border-r-grau2 border-r-1 border-b-grau2 border-b-2"> <!-- HEADER - ACCOUNT SETTINGS-->
<h1 class="text-weiss text-3xl p-4">Account</h1>
</div>
<div class="flex flex-row content-center justify-center autofill:"> <!-- BODY - ACCOUNT SETTINGS-->
<settings_account class="basis-1/2" @updateAccountSetting="handleUpdateAccountSetting"></settings_account>
<settings_account_main class="basis-1/2" :selectedAccountSetting="selectedAccountSetting"></settings_account_main>
</div>
</div>
<div v-else-if="props.selectedSetting === 'setting_privacy'"> <!-- PRIVACY AND SAFTEY-->
<div class="border-r-grau2 border-r-2 border-b-grau2 border-b-2"> <!-- HEADER PRIVACY-->
<h1 class="text-weiss text-3xl p-4">Privacy and Saftey</h1>
</div>
<div> <!-- BODY - ACCOUNT SETTINGS-->
<sps_main></sps_main>
</div>
</div>
<div v-else-if="props.selectedSetting === 'setting_messages'"> <!-- MESSAGES-->
<label class="text-center">Hey! So you found this setting? </label>
</div>
<div v-else-if="props.selectedSetting === 'setting_other'"> <!-- OTHER -->
<div class="border-r-grau2 border-r-1 border-b-grau2 border-b-2"> <!-- HEADER - ACCOUNT SETTINGS-->
<h1 class="text-weiss text-3xl p-4">Other</h1>
</div>
<so_main></so_main>
</div>
<div v-else-if="props.selectedSetting === undefined"> <!-- IF NOTHING SELECTED-->
<label>Select a Setting in the Sidebar</label>
</div>
<div v-else-if="props.selectedSetting === ''"> <!-- IF NOTHING SELECTED-->
<div class="border-r-grau2 border-r-1 border-b-grau2 border-b-2"> <!-- HEADER - ACCOUNT SETTINGS-->
<h1 class="text-weiss text-3xl p-4">Account</h1>
</div>
<div class="flex flex-row content-center justify-center"> <!-- BODY - ACCOUNT SETTINGS-->
<settings_account class="basis-1/2" @updateAccountSetting="handleUpdateAccountSetting"></settings_account>
<settings_account_main class="basis-1/2" :selectedAccountSetting="selectedAccountSetting"></settings_account_main>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { defineEmits } from 'vue';
import { ref } from 'vue';
const emit = defineEmits(['updateSetting']);
const selectedSetting = ref('');
function updateSetting(setting: string) {
selectedSetting.value = setting;
emit('updateSetting', setting);
console.log(`Setting selected (SS): ${setting}`);
}
</script>
<template>
<div class="text-grau2 font-bold space-y-4 pl-2 pt-5">
<label class="flex text-center rounded-lg" :class="{'text-weiss': selectedSetting === 'setting_account' || selectedSetting === ''}" @click="updateSetting('setting_account')"><img class="pr-2" src="../../assets/icons/user.png" alt="">Account</label>
<label class="flex text-center rounded-lg" :class="{'text-weiss': selectedSetting === 'setting_privacy'}" @click="updateSetting('setting_privacy')"><img class="pr-2" src="../../assets/icons/shield.png" alt="">Privacy and Saftey</label>
<label class="flex text-center rounded-lg" :class="{'text-weiss': selectedSetting === 'setting_message'}" @click="updateSetting('setting_messages')"><img class="pr-2" src="../../assets/icons/mail.png" alt="">Messages</label>
<label class="flex text-center rounded-lg" :class="{'text-weiss': selectedSetting === 'setting_other'}" @click="updateSetting('setting_other')"><img class="pr-2" src="../../assets/icons/other.png" alt="">Other</label>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
import router from "../../router";
function logout() {
console.log("Logout");
localStorage.setItem('isLoggedIn', 'false');
router.push("/login");
}
</script>
<template>
<div class="text-grau2 font-bold space-y-4 pl-2 pt-5 mb-4">
<label class="flex text-center shadow-2xl rounded-lg active:text-weiss" @click="logout"><img class="pr-2" src="../../assets/icons/logout.png" alt="">Logout</label>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,17 @@
<script setup>
</script>
<template>
<div class="m-3 flex flex-col">
<label class="text-xl text-grau2 m-2">Blocked Users</label>
<label class="text-xl text-grau2 m-2">Mailing</label>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,10 +1,7 @@
<script setup> <script setup>
import Navigationbar from "./home_components/navigationbar.vue"; import Navigationbar from "./home_components/navigationbar.vue";
import Feed from "./home_components/feed.vue";
import Contacts from "./home_components/contacts.vue"; import Contacts from "./home_components/contacts.vue";
import Legal from "./home_components/legal.vue"; import Legal from "./home_components/legal.vue";
import Trending from "./home_components/trending.vue";
import QuickSearch from "./home_components/quick_search.vue";
</script> </script>
<template> <template>
@@ -16,7 +13,6 @@ import QuickSearch from "./home_components/quick_search.vue";
<label class="text-weiss text-5xl align-middle mx-60 pt-60">This site is currently Work in Progress...</label> <label class="text-weiss text-5xl align-middle mx-60 pt-60">This site is currently Work in Progress...</label>
</div> </div>
<div id="right"> <div id="right">
<quick-search> </quick-search>
<contacts></contacts> <contacts></contacts>
<legal></legal> <legal></legal>
</div> </div>

View File

@@ -1,5 +1,5 @@
import './assets/main.css'; import './assets/main.css';
import { createApp } from 'vue'; import {createApp, onMounted} from 'vue';
// @ts-ignore: // @ts-ignore:
import App from '../src/App.vue'; import App from '../src/App.vue';
// @ts-ignore: // @ts-ignore:

View File

@@ -1,35 +1,90 @@
// File: `src/router/index.ts`
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import Home from "../../src/components/Home.vue";
// Vue components imported here.
// the vue components are the Pages that will be rendered
// at these URL's.
import Home from "../components/Home.vue";
import Login from "../components/Login.vue"; import Login from "../components/Login.vue";
import wip from "../components/wip.vue"; import wip from "../components/wip.vue";
import settings from "../components/settings.vue";
import notifications from "../components/notifications.vue";
import register from "../components/register.vue";
import search from "../components/search.vue";
import post from "../components/posts.vue";
import profile from "../components/profile.vue";
import messages from "../components/messages.vue";
// The routing does not happen automatically
// Each route has to be defined here, or it wont work.
const routes = [ const routes = [
{ {
path: "/", path: "/",
name: "home", name: "home",
component: Home, component: Home,
meta: { requiresAuth: true }
}, },
{ {
path: "/login", path: "/login",
name: "login", name: "login",
component: Login, component: Login
}, },
{ {
path: "/wip", path: "/wip",
name: "Work in Progress", name: "Work in Progress",
component: wip, component: wip,
meta: { requiresAuth: true }
}, },
] {
path: "/settings",
name: "Settings",
component: settings,
meta: { requiresAuth: true }
},
{
path: "/notifications",
name: "Notifications",
component: notifications,
meta: { requiresAuth: true }
},
{
path: "/register",
name: "Register",
component: register
},
{
path: "/search",
name: "Search",
component: search,
meta: { requiresAuth: true }
},
{
path: "/post/:id",
name: "PostDetail",
component: post,
meta: { requiresAuth: true }
},
{
path: "/profile/:username",
name: "Profile",
component: profile,
props: true,
meta: { requiresAuth: true }
},
{
path: "/messages",
name: "Messages",
component: messages,
meta: { requiresAuth: true }
}
];
const router = createRouter({ const router = createRouter({
history: createWebHistory("/"), history: createWebHistory("/"),
routes, routes,
}); });
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && localStorage.getItem("isLoggedIn") !== "true") {
alert("not logged in ");
next({ name: "login" });
} else {
next();
}
});
export default router; export default router;

View File

@@ -21,7 +21,8 @@ export default {
'35p': '30.825%', '35p': '30.825%',
} }
}, },
colors: { plugins: ['tailwind-scrollbar', 'tailwind-scrollbar-hide'],
colors: {
'logo-farbe-lila': '#5500a2', 'logo-farbe-lila': '#5500a2',
'logo-farbe-rot': '#a2002b', 'logo-farbe-rot': '#a2002b',
'logo-farbe-blau': '#0b1074', 'logo-farbe-blau': '#0b1074',