Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94c14226dc | ||
|
|
0f9284d515 | ||
|
|
4be4a26036 | ||
|
|
ed70ace33d | ||
|
|
dc29170a58 | ||
|
|
c2f56b2b5d | ||
|
|
86bc0347ca | ||
|
|
21c77cd34a | ||
|
|
8f27246234 | ||
|
|
fb916e9e9f | ||
|
|
3cd92d7fff | ||
|
|
915e91fbd1 | ||
|
|
9a8237de42 | ||
|
|
5a5cf84197 | ||
|
|
7832267081 | ||
|
|
96f9071edd | ||
|
|
f1b8dfe92f | ||
|
|
834149ffb9 | ||
|
|
c2d5cc6a73 | ||
|
|
2d29b9a3a4 | ||
|
|
a5fa8d840c | ||
|
|
932edc5a2b | ||
|
|
7331b1b9b0 | ||
|
|
c7365703df | ||
|
|
4411c7def6 | ||
|
|
eca851dc08 | ||
|
|
9af5b34658 | ||
|
|
bb2cfedb84 | ||
|
|
8b4b99a015 | ||
|
|
1d8665d6ce | ||
|
|
aa690f0989 | ||
|
|
3683be8202 | ||
|
|
d0182e7d5b | ||
|
|
5bf7d62f71 | ||
|
|
f478198ff7 | ||
|
|
dc79759d38 | ||
|
|
17a23a992e | ||
|
|
2352c25c05 | ||
|
|
860cd027cd |
41
.github/workflows/deploy.yml
vendored
Normal 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
@@ -12,6 +12,7 @@ dist
|
||||
dist-ssr
|
||||
.vite
|
||||
*.local
|
||||
*.sqlite
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
||||
28
README.md
@@ -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.
|
||||
|
||||
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?
|
||||
|
||||
The only dependency is Deno! Nothing else is required, Deno will install all the Modules needed and start it.
|
||||
|
||||
## Running
|
||||
|
||||
Dev server:
|
||||
API + Website:
|
||||
|
||||
```bash
|
||||
$ deno task dev
|
||||
```
|
||||
|
||||
API only:
|
||||
```bash
|
||||
$ deno task dev:api
|
||||
@@ -47,24 +50,21 @@ $ deno task build
|
||||
|
||||
|
||||
## Libs/Frameworks used
|
||||
<ul>
|
||||
<li><a href="https://deno.land/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Deno.svg/600px-Deno.svg.png" width="20" height="20"> Deno</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://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://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>
|
||||
<ul>
|
||||
<li><a href="https://deno.land/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Deno.svg/600px-Deno.svg.png" width="20" height="20"> Deno</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://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://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://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>
|
||||
|
||||
## Tools used
|
||||
|
||||
|
||||
|
||||
<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/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://code.visualstudio.com/"><img src="https://code.visualstudio.com/assets/images/code-stable.png" width="20" height="20"> Visual Studio Code</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
};
|
||||
317
api/main.ts
@@ -1,44 +1,307 @@
|
||||
// main API file. Handles all the routing/api stuff
|
||||
|
||||
// Due to the Language servers, the import statements are
|
||||
// shown as errors, @ts-ignore is used to ignore them.
|
||||
// This is a Deno file, but the Vue LSP is still
|
||||
// attempting to find errors, which causes
|
||||
// confusing False errors
|
||||
/// <reference lib="deno.ns" />
|
||||
/**
|
||||
* @author Esad Mustafoski
|
||||
* @description Main API file, Handles all the routing/api stuff
|
||||
*/
|
||||
|
||||
// +++ IMPORTS ------------------------------------------------------ //
|
||||
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
|
||||
import { oakCors } from "https://deno.land/x/cors/mod.ts";
|
||||
import * as db_utils from "../database/utils.ts";
|
||||
import {
|
||||
Application,
|
||||
Context,
|
||||
Next,
|
||||
Router,
|
||||
} from "https://deno.land/x/oak@v17.1.2/mod.ts";
|
||||
import { oakCors } from "https://deno.land/x/cors@v1.2.2/mod.ts";
|
||||
import {
|
||||
dirname,
|
||||
fromFileUrl,
|
||||
join,
|
||||
} from "https://deno.land/std@0.224.0/path/mod.ts";
|
||||
|
||||
// +++ 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 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 ------------------------------------------------------- //
|
||||
// Creates the routes for the API server.
|
||||
// Example: localhost:8000/api will show "testAPIPoint"
|
||||
// in the HTML
|
||||
// in the HTML page.
|
||||
|
||||
// Docs Routes
|
||||
router
|
||||
.get("/", (ctx) => {
|
||||
ctx.response.body = "ESP API Site";
|
||||
})
|
||||
.get("/api", (ctx) => {
|
||||
ctx.response.body = "testAPIPoint";
|
||||
})
|
||||
.get("/api/users", (ctx) => {
|
||||
ctx.response.body = "Info from all users here"; //getAllUsers();
|
||||
})
|
||||
.get("/api/posts", async (ctx) => {
|
||||
const getPosts = await db_utils.getPostsFromDB();
|
||||
const countedPosts = await db_utils.countPosts();
|
||||
ctx.response.body = { getPosts, countedPosts };
|
||||
.get("/", (ctx: any) => {
|
||||
ctx.response.body = "For endpoints, use /api/{name}";
|
||||
})
|
||||
.get("/api", (ctx: any) => {
|
||||
ctx.response.body = "For API Documentation, visit /docs";
|
||||
});
|
||||
|
||||
// -- Account routes --
|
||||
router
|
||||
.post("/api/account/login", api_login)
|
||||
.post("/api/account/register", api_register)
|
||||
.post("/api/account/logout", () => {}) // TODO
|
||||
.post("/api/account/password/forgot", () => {}) // TODO
|
||||
.post("/api/account/password/reset", () => {}) // TODO
|
||||
.post("/api/account/password/change", () => {}) // TODO
|
||||
.post("/api/account/email/change-email", () => {}) // TODO
|
||||
.post("/api/account/email/verify-email", () => {}) // TODO
|
||||
.post("/api/account/delete-account", () => {}) // TODO
|
||||
.post("/api/account/block", () => {}); // TODO
|
||||
|
||||
// -- Auth Routes -- //
|
||||
router
|
||||
.get("/api/auth", () => {}) // TODO
|
||||
.get("/api/auth/verify", () => {}) // TODO
|
||||
.get("/api/auth/refresh", () => {}); // TODO
|
||||
|
||||
// -- User routes -- //
|
||||
router.get("/api/users", api_getAllUsers);
|
||||
// .get("/api/user/:id/info", api_user_getInfo);
|
||||
|
||||
// -- Chat routes -- //
|
||||
router
|
||||
.get("/api/chats", api_getChats)
|
||||
.get("/api/chat/:id", api_getChatById)
|
||||
.post("/api/chat/create", api_createChat)
|
||||
.post("/api/chat/:id/message", api_sendMessage)
|
||||
.delete("/api/chat/:id", api_deleteChat);
|
||||
|
||||
// -- Post routes -- //
|
||||
router
|
||||
.get("/api/posts", api_posts_getAll)
|
||||
.get("/api/post/:id", api_getPostById)
|
||||
.post("/api/post/create", api_createPost)
|
||||
.put("/api/post/:id", api_updatePost)
|
||||
.delete("/api/post/:id", api_deletePost)
|
||||
.post("/api/post/:id/like", api_likePost);
|
||||
|
||||
// -- Comment Routes -- //
|
||||
router
|
||||
.get("/api/post/:id/comments", api_getPostComments)
|
||||
.post("/api/post/:id/comment", api_createComment)
|
||||
.put("/api/comment/:id", api_updateComment)
|
||||
.delete("/api/comment/:id", api_deleteComment)
|
||||
.post("/api/comment/:id/like", api_likeComment);
|
||||
|
||||
// +++ FUNCTIONS ----------------------------------------------------- //
|
||||
|
||||
// ABANDONED FUNCTIONS //
|
||||
async function _authenticator(ctx: Context, next: Next): Promise<void> {
|
||||
const authHeader = ctx.request.headers.get("Authorization");
|
||||
|
||||
if (!authHeader) {
|
||||
ctx.response.status = 401;
|
||||
ctx.response.body = { error: "No header" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Bearer check
|
||||
// Bearer is often used for authentication in API's and is a standard, I check it using RegEx (Regular Expressions)
|
||||
const match = authHeader.match(/^Bearer (.+)$/);
|
||||
if (!match) {
|
||||
ctx.response.status = 401;
|
||||
ctx.response.body = { error: "Invalid format" };
|
||||
return;
|
||||
}
|
||||
|
||||
const token = match[1];
|
||||
|
||||
try {
|
||||
// Token logic missing, unattempted.
|
||||
await next();
|
||||
} catch (error) {
|
||||
ctx.response.status = 401;
|
||||
ctx.response.body = { error: "Invalid token" };
|
||||
}
|
||||
}
|
||||
|
||||
async function _tokenChecker(ctx: Context, next: Next): Promise<void> {
|
||||
// logic below (TODO):
|
||||
/**
|
||||
* 1. check if the token is valid
|
||||
* 2. if valid, set the user in the context
|
||||
* 3. if not valid, return 401
|
||||
* 4. if: token missing, expired, blacklisted, !DB, !user or !correct user, return 401 with associated error
|
||||
* eg: wrong user: 401 -> "Token not associated with this user"
|
||||
*/
|
||||
}
|
||||
|
||||
// API: Posts //
|
||||
async function api_posts_getAll(ctx: Context): Promise<void> {
|
||||
const posts = await db_utils.getPostsFromDB();
|
||||
ctx.response.body = posts;
|
||||
}
|
||||
|
||||
// API: login/register //
|
||||
async function api_register(ctx: Context): Promise<void> {
|
||||
try {
|
||||
const body = ctx.request.body;
|
||||
const result = await body.json();
|
||||
const {
|
||||
username,
|
||||
password,
|
||||
userGroup,
|
||||
displayname,
|
||||
user_email,
|
||||
firstname,
|
||||
surname,
|
||||
} = result;
|
||||
// Claude 3-5 Sonnet was used for the first Date formatting
|
||||
const account_created = `${Math.floor(Date.now() / 1000)}-${
|
||||
new Date()
|
||||
.toLocaleDateString("en-GB")
|
||||
.split("/")
|
||||
.join("-")
|
||||
}`;
|
||||
|
||||
if (
|
||||
!username ||
|
||||
!password ||
|
||||
!userGroup ||
|
||||
!displayname ||
|
||||
!user_email ||
|
||||
!firstname ||
|
||||
!surname
|
||||
) {
|
||||
helper_utils.errorResponse(ctx, 400, "Missing required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
// First salt the password
|
||||
const { saltedPassword, salt } = await helper_utils.saltPassword(password);
|
||||
// Then hash the salted password
|
||||
const hash = await helper_utils.hashPassword(saltedPassword);
|
||||
|
||||
const userId = db_utils.registerUser(
|
||||
username,
|
||||
hash,
|
||||
salt,
|
||||
userGroup,
|
||||
displayname,
|
||||
user_email,
|
||||
firstname,
|
||||
surname,
|
||||
account_created,
|
||||
);
|
||||
|
||||
const user = await db_utils.getUserByUsername(username);
|
||||
|
||||
const responseBody: any = {
|
||||
success: true,
|
||||
message: "Register successful",
|
||||
};
|
||||
|
||||
if (user.user_id !== undefined) {
|
||||
responseBody.userId = user.user_id;
|
||||
}
|
||||
|
||||
helper_utils.sendResponse(ctx, {
|
||||
status: 200,
|
||||
body: responseBody,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
helper_utils.errorResponse(ctx, 500, "Invalid request");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function api_login(ctx: Context): Promise<string> {
|
||||
try {
|
||||
const body = ctx.request.body;
|
||||
const result = await body.json();
|
||||
const { username, password } = result;
|
||||
|
||||
if (!username || !password) {
|
||||
helper_utils.errorResponse(ctx, 400, "Missing required fields");
|
||||
return "Error";
|
||||
}
|
||||
|
||||
const user = await db_utils.getUserByUsername(username);
|
||||
if (!user) {
|
||||
helper_utils.errorResponse(ctx, 404, "User not found");
|
||||
return "Error";
|
||||
}
|
||||
|
||||
const storedSalt = user.password_salt;
|
||||
const saltedPassword = `${password}${storedSalt}`;
|
||||
const hash = await helper_utils.hashPassword(saltedPassword);
|
||||
|
||||
// Compare the hashed password with the stored hash
|
||||
if (user.password !== hash) {
|
||||
helper_utils.errorResponse(ctx, 401, "Invalid password");
|
||||
return "Error";
|
||||
}
|
||||
|
||||
// Return success with the user ID if it exists
|
||||
const responseBody: any = {
|
||||
success: true,
|
||||
message: "Login successful",
|
||||
};
|
||||
|
||||
// Only add userId if it exists
|
||||
if (user.user_id !== undefined) {
|
||||
responseBody.userId = user.user_id;
|
||||
}
|
||||
|
||||
helper_utils.sendResponse(ctx, {
|
||||
status: 200,
|
||||
body: responseBody,
|
||||
});
|
||||
|
||||
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.allowedMethods());
|
||||
|
||||
// @ts-ignore: start app
|
||||
export { app };
|
||||
await app.listen({ port: 8000 });
|
||||
|
||||
104
database/create_db.ts
Normal 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)
|
||||
`,
|
||||
);
|
||||
}
|
||||
0
database/doc/DATABASE_UTILS.md
Normal file
61
database/helpers/interfaces.ts
Normal 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 };
|
||||
122
database/helpers/maphelper.ts
Normal 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
|
||||
};
|
||||
86
database/helpers/utils/chat_utils.ts
Normal 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
|
||||
};
|
||||
81
database/helpers/utils/comment_utils.ts
Normal 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,
|
||||
};
|
||||
46
database/helpers/utils/core_utils.ts
Normal 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 };
|
||||
13
database/helpers/utils/mod.ts
Normal 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";
|
||||
128
database/helpers/utils/post_utils.ts
Normal 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,
|
||||
};
|
||||
103
database/helpers/utils/user_utils.ts
Normal 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
@@ -1,138 +1,208 @@
|
||||
import { DB } from "https://deno.land/x/sqlite/mod.ts";
|
||||
import { dirname, fromFileUrl, join } from "https://deno.land/std/path/mod.ts";
|
||||
/// <reference lib="deno.ns" />
|
||||
|
||||
/**
|
||||
* @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
|
||||
// from SQLite doesn't like relative paths, so I use this as
|
||||
// +++ 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
|
||||
// A Workaround
|
||||
const __dirname:string = dirname(fromFileUrl(import.meta.url));
|
||||
const dbPath:string = join(__dirname, "../database/esp-projekt.sqlite");
|
||||
const _dirname: string = dirname(fromFileUrl(import.meta.url));
|
||||
const dbPath: string = join(_dirname, "../database/esp-projekt.sqlite");
|
||||
const db = new DB(dbPath);
|
||||
|
||||
// Interfaces
|
||||
interface Post {
|
||||
posts_uuid: number;
|
||||
user_id: number;
|
||||
created_at: string;
|
||||
post_text: string;
|
||||
likes: number;
|
||||
comments: number;
|
||||
// +++ INTERFACES --------------------------------------------------- //
|
||||
// Only re-export interfaces that are needed by external code
|
||||
export type {
|
||||
Accounts,
|
||||
Chat,
|
||||
Comments,
|
||||
Message,
|
||||
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() {
|
||||
let dataresult: Array<Post> = [];
|
||||
try {
|
||||
try {
|
||||
const rows = await db.query("SELECT * FROM posts");
|
||||
|
||||
// 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
|
||||
});
|
||||
}
|
||||
await Deno.stat(dbDir);
|
||||
} 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 {
|
||||
for (const [c] of await db.query("SELECT COUNT(*) FROM posts")) {
|
||||
count = c as number;
|
||||
}
|
||||
dbInstance.query("SELECT 1 FROM marker LIMIT 1;");
|
||||
console.log("Database already initialized (marker table found).");
|
||||
} 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);
|
||||
return count;
|
||||
}
|
||||
|
||||
function getCommentsForPost(postid: bigint) {
|
||||
}
|
||||
|
||||
function getCommentsForComments(commentid: bigint) {
|
||||
}
|
||||
|
||||
function getAllUsers() {
|
||||
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,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error during database existence check or initialization:",
|
||||
error,
|
||||
);
|
||||
} finally {
|
||||
if (dbInstance) {
|
||||
dbInstance.close();
|
||||
console.log("Database connection closed.");
|
||||
} else {
|
||||
console.log("Database connection was not opened.");
|
||||
}
|
||||
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
|
||||
function filterForImagePosts() {
|
||||
return [];
|
||||
}
|
||||
// +++ COMMENT FUNCTIONS -------------------------------------------- //
|
||||
export const getCommentsFromDB = (post_id?: number) =>
|
||||
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() {
|
||||
return [];
|
||||
}
|
||||
// +++ CHAT FUNCTIONS ----------------------------------------------- //
|
||||
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() {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Export all Functions to make this a module
|
||||
export {
|
||||
getPostsFromDB,
|
||||
countPosts,
|
||||
getCommentsForPost,
|
||||
getCommentsForComments,
|
||||
getAllUsers,
|
||||
getUserByID,
|
||||
getAllPostsFromUser,
|
||||
filterForImagePosts,
|
||||
filterForVideoPosts,
|
||||
filterForTextPosts
|
||||
};
|
||||
// +++ UTILITY FUNCTIONS -------------------------------------------- //
|
||||
export { filterPosts };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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:vite": "deno run -A npm:vite",
|
||||
"build": "deno run -A --node-modules-dir npm:vite build",
|
||||
|
||||
180
deno.lock
generated
@@ -6,6 +6,7 @@
|
||||
"jsr:@std/bytes@1": "1.0.2",
|
||||
"jsr:@std/bytes@^1.0.2": "1.0.2",
|
||||
"jsr:@std/crypto@1": "1.0.3",
|
||||
"jsr:@std/encoding@*": "1.0.5",
|
||||
"jsr:@std/encoding@1": "1.0.5",
|
||||
"jsr:@std/encoding@^1.0.5": "1.0.5",
|
||||
"jsr:@std/http@1": "1.0.9",
|
||||
@@ -22,12 +23,17 @@
|
||||
"npm:autoprefixer@^10.4.20": "10.4.20_postcss@8.4.47",
|
||||
"npm:path-to-regexp@6.2.1": "6.2.1",
|
||||
"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": "3.4.14_postcss@8.4.47",
|
||||
"npm:typescript@^5.8.2": "5.8.2",
|
||||
"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": "5.4.10",
|
||||
"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": "3.5.12"
|
||||
},
|
||||
@@ -78,6 +84,14 @@
|
||||
"@alloc/quick-lru@5.2.0": {
|
||||
"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": {
|
||||
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="
|
||||
},
|
||||
@@ -100,7 +114,7 @@
|
||||
"@deno/vite-plugin@1.0.0_vite@5.4.10": {
|
||||
"integrity": "sha512-Q9UeWqs3s7B5lqzu1Z5QrzYAzqTj3+F9YW17tWobGRbT2G40ihwis6zK/+QgMgcG4fm3IqdIfXmpQYhkZpdMfw==",
|
||||
"dependencies": [
|
||||
"vite"
|
||||
"vite@5.4.10"
|
||||
]
|
||||
},
|
||||
"@esbuild/aix-ppc64@0.21.5": {
|
||||
@@ -290,11 +304,14 @@
|
||||
"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": {
|
||||
"integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==",
|
||||
"dependencies": [
|
||||
"vite",
|
||||
"vue"
|
||||
"vite@5.4.10",
|
||||
"vue@3.5.12"
|
||||
]
|
||||
},
|
||||
"@vue/compiler-core@3.5.12": {
|
||||
@@ -365,7 +382,15 @@
|
||||
"dependencies": [
|
||||
"@vue/compiler-ssr",
|
||||
"@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": {
|
||||
@@ -393,7 +418,7 @@
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dependencies": [
|
||||
"normalize-path",
|
||||
"picomatch"
|
||||
"picomatch@2.3.1"
|
||||
]
|
||||
},
|
||||
"arg@5.0.2": {
|
||||
@@ -464,9 +489,18 @@
|
||||
"is-binary-path",
|
||||
"is-glob",
|
||||
"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": {
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": [
|
||||
@@ -482,7 +516,7 @@
|
||||
"cross-spawn@7.0.3": {
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dependencies": [
|
||||
"path-key",
|
||||
"path-key@3.1.1",
|
||||
"shebang-command",
|
||||
"which"
|
||||
]
|
||||
@@ -581,6 +615,12 @@
|
||||
"reusify"
|
||||
]
|
||||
},
|
||||
"fdir@6.4.3_picomatch@4.0.2": {
|
||||
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
|
||||
"dependencies": [
|
||||
"picomatch@4.0.2"
|
||||
]
|
||||
},
|
||||
"fill-range@7.1.1": {
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dependencies": [
|
||||
@@ -700,6 +740,9 @@
|
||||
"jiti@1.21.6": {
|
||||
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w=="
|
||||
},
|
||||
"js-tokens@4.0.0": {
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"lilconfig@2.1.0": {
|
||||
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="
|
||||
},
|
||||
@@ -725,7 +768,7 @@
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dependencies": [
|
||||
"braces",
|
||||
"picomatch"
|
||||
"picomatch@2.3.1"
|
||||
]
|
||||
},
|
||||
"minimatch@9.0.5": {
|
||||
@@ -757,6 +800,13 @@
|
||||
"normalize-range@0.1.2": {
|
||||
"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": {
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
||||
},
|
||||
@@ -772,6 +822,9 @@
|
||||
"path-key@3.1.1": {
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
|
||||
},
|
||||
"path-key@4.0.0": {
|
||||
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="
|
||||
},
|
||||
"path-parse@1.0.7": {
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||
},
|
||||
@@ -791,6 +844,9 @@
|
||||
"picomatch@2.3.1": {
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
|
||||
},
|
||||
"picomatch@4.0.2": {
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="
|
||||
},
|
||||
"pify@2.3.0": {
|
||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
|
||||
},
|
||||
@@ -846,6 +902,14 @@
|
||||
"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": {
|
||||
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
|
||||
},
|
||||
@@ -858,6 +922,9 @@
|
||||
"queue-microtask@1.2.3": {
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
|
||||
},
|
||||
"react@19.0.0": {
|
||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="
|
||||
},
|
||||
"read-cache@1.0.0": {
|
||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||
"dependencies": [
|
||||
@@ -867,9 +934,12 @@
|
||||
"readdirp@3.6.0": {
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dependencies": [
|
||||
"picomatch"
|
||||
"picomatch@2.3.1"
|
||||
]
|
||||
},
|
||||
"readdirp@4.1.1": {
|
||||
"integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw=="
|
||||
},
|
||||
"resolve@1.22.8": {
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"dependencies": [
|
||||
@@ -990,12 +1060,25 @@
|
||||
"supports-preserve-symlinks-flag@1.0.0": {
|
||||
"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": {
|
||||
"integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
|
||||
"dependencies": [
|
||||
"@alloc/quick-lru",
|
||||
"arg",
|
||||
"chokidar",
|
||||
"chokidar@3.6.0",
|
||||
"didyoumean",
|
||||
"dlv",
|
||||
"fast-glob",
|
||||
@@ -1017,6 +1100,9 @@
|
||||
"sucrase"
|
||||
]
|
||||
},
|
||||
"tailwindcss@4.0.15": {
|
||||
"integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg=="
|
||||
},
|
||||
"thenify-all@1.6.0": {
|
||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||
"dependencies": [
|
||||
@@ -1029,6 +1115,16 @@
|
||||
"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": {
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dependencies": [
|
||||
@@ -1038,9 +1134,15 @@
|
||||
"ts-interface-checker@0.1.13": {
|
||||
"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": {
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||
},
|
||||
"unicorn-magic@0.3.0": {
|
||||
"integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="
|
||||
},
|
||||
"update-browserslist-db@1.1.1_browserslist@4.24.2": {
|
||||
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
|
||||
"dependencies": [
|
||||
@@ -1059,6 +1161,21 @@
|
||||
"util-deprecate@1.0.2": {
|
||||
"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": {
|
||||
"integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
|
||||
"dependencies": [
|
||||
@@ -1068,11 +1185,31 @@
|
||||
"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": {
|
||||
"integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==",
|
||||
"dependencies": [
|
||||
"@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": {
|
||||
@@ -1081,10 +1218,21 @@
|
||||
"@vue/compiler-dom",
|
||||
"@vue/compiler-sfc",
|
||||
"@vue/runtime-dom",
|
||||
"@vue/server-renderer",
|
||||
"@vue/server-renderer@3.5.12_vue@3.5.12",
|
||||
"@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": {
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dependencies": [
|
||||
@@ -1120,6 +1268,7 @@
|
||||
"remote": {
|
||||
"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/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/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2",
|
||||
"https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c",
|
||||
@@ -1266,7 +1415,12 @@
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<!doctype html>
|
||||
<!Doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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" />
|
||||
<title>Vite + Vue + TS</title>
|
||||
<title>ESP - Express, Share, Post</title>
|
||||
<link rel="stylesheet" href="/src/assets/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<body class="bg-hintergrund-farbe sm:overflow-hidden">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
991
package-lock.json
generated
@@ -1,6 +1,13 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<template>
|
||||
<template >
|
||||
<router-view />
|
||||
</template>
|
||||
BIN
src/assets/icons/logout.png
Normal file
|
After Width: | Height: | Size: 408 B |
BIN
src/assets/icons/other.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
src/assets/icons/send.png
Normal file
|
After Width: | Height: | Size: 587 B |
BIN
src/assets/icons/shield.png
Normal file
|
After Width: | Height: | Size: 501 B |
BIN
src/assets/icons/trash.png
Normal file
|
After Width: | Height: | Size: 271 B |
BIN
src/assets/icons/x-klein.png
Normal file
|
After Width: | Height: | Size: 340 B |
BIN
src/assets/icons/x.png
Normal file
|
After Width: | Height: | Size: 538 B |
@@ -1,3 +1,5 @@
|
||||
@plugin 'tailwind-scrollbar';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@@ -4,21 +4,18 @@ import Contacts from "./home_components/contacts.vue";
|
||||
import Legal from "./home_components/legal.vue";
|
||||
import Navigationbar from "./home_components/navigationbar.vue";
|
||||
import Trending from "./home_components/trending.vue";
|
||||
import QuickSearch from "./home_components/quick_search.vue";
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div id="main" class="bg-hintergrund-farbe flex">
|
||||
<div id="left" class="border border-b-grau w-72">
|
||||
<div id="main" class="sm:flex overflow-y-auto sm:h-full sm:scrollbar">
|
||||
<div id="left" class="sm:w-72 min-w-72">
|
||||
<navigationbar></navigationbar>
|
||||
<trending></trending>
|
||||
</div>
|
||||
<div class="w-100p">
|
||||
<div class="sm:w-100p w-screen">
|
||||
<feed></feed>
|
||||
</div>
|
||||
<div class="w-1/4">
|
||||
<quick-search></quick-search>
|
||||
<div class="sm:w-1/4 w-screen">
|
||||
<contacts></contacts>
|
||||
<legal></legal>
|
||||
</div>
|
||||
|
||||
@@ -2,15 +2,21 @@
|
||||
|
||||
import Legal from "./home_components/legal.vue";
|
||||
import Login_comp from "./login_components/login_comp.vue";
|
||||
import router from "../router";
|
||||
|
||||
function route_home() {
|
||||
router.push("/");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="main" class="bg-hintergrund-farbe flex p-2 ">
|
||||
<div class="items-center pr-96 pl-2 pt-5">
|
||||
<img src="../assets/esp-logo_no_text.png" alt="" class="rounded-lg h-12 w-24 mx-auto">
|
||||
<div id="main" class="bg-hintergrund-farbe sm:flex sm:p-2 sm:justify-between ">
|
||||
<div class="pt-5 pl-2">
|
||||
<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>
|
||||
<login_comp></login_comp>
|
||||
<div class="p-5 ">
|
||||
<login_comp class="inset-0"></login_comp>
|
||||
<div class="md:inset-y-0 sm:right-0 sm:max-w-36 text-center">
|
||||
<legal></legal>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
@@ -1,9 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
const contacts =[
|
||||
{display_name: "Linux Enjoyer", username: "lunix"},
|
||||
{display_name: "XBOX", username: "Xbox"},
|
||||
{display_name: "JETBrains", username: "Jetbrains"},
|
||||
]
|
||||
import { ref } from 'vue';
|
||||
import Popup_chat from "./popup_chat.vue";
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -13,19 +25,19 @@ const contacts =[
|
||||
</div>
|
||||
<div> <!--CONTENT-->
|
||||
<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-->
|
||||
<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="">
|
||||
<label class=" font-bold m-1 text-weiss">{{ contact.display_name }}</label><br>
|
||||
<p class="text-base m-1 text-grau2 underline-offset-3">@{{ contact.username }}</p>
|
||||
<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> <!-- username-->
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<popup_chat v-if="showChatPopup" :contact="selectedContact" @close="showChatPopup = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,105 +1,176 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
// import {valueOf} from "tailwindcss";
|
||||
const post = ref([
|
||||
{id: 1,
|
||||
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.",
|
||||
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},
|
||||
])
|
||||
import {onMounted, ref} from "vue";
|
||||
import router from "../../router";
|
||||
// PLACEHOLDER
|
||||
const upc = ref([]); // user post computed
|
||||
let post_create_text = "";
|
||||
let self_id ;
|
||||
|
||||
const addLike = (index: number) => {
|
||||
post.value[index].likes += 1;
|
||||
console.log("New Like Amount: ", post.value[index].likes);
|
||||
onMounted(async () => {
|
||||
self_id = localStorage.getItem("self_id");
|
||||
await createFeed();
|
||||
});
|
||||
|
||||
}
|
||||
function post_publish_func(text:string) {
|
||||
console.log("Post: ", text);
|
||||
if (text === undefined || text === "") {
|
||||
console.log("Post is empty");
|
||||
alert("Post is empty");
|
||||
async function createFeed() {
|
||||
try {
|
||||
// posts und user holen und schauen ob sie richtig sidn
|
||||
const post_response = await fetch('http://localhost:8000/api/posts', { method: 'GET' });
|
||||
const postsDATA = await post_response.json();
|
||||
|
||||
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;
|
||||
} 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>
|
||||
|
||||
<template>
|
||||
<div class="border-x-2 border-x-grau2"> <!-- MAIN -->
|
||||
<div class="sm:border-x sm:border-x-grau2"> <!-- MAIN -->
|
||||
<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-->
|
||||
<div class="flex border-2 border-b-grau2">
|
||||
<img src="../../assets/danielvici_pp.png" alt="" class="p-2 rounded-full w-16 h-16">
|
||||
<form>
|
||||
<img v-if="self_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">
|
||||
<form class="w-full pr-5">
|
||||
<!-- 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="">
|
||||
<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_publish_func(post_publish)" 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_create(post_create_text)" type="button">Post</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div> <!-- CONTENT -->
|
||||
<div class="sm:overflow-y-auto sm:h-[650px] sm:scrollbar"> <!-- CONTENT -->
|
||||
<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 -->
|
||||
<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> <!-- POST HEADER -->
|
||||
<label class="text-lg font-bold m-1 text-weiss">{{postitem.author_display_name}}</label>
|
||||
<label class="text-base m-1 underline-offset-3 text-grau2">@{{ postitem.author_username }}</label>
|
||||
<label class="text-lg font-bold m-1 text-weiss">{{postitem.displayname}}</label>
|
||||
<label class="text-base m-1 text-grau2">@{{ postitem.username }}</label>
|
||||
</div>
|
||||
<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 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="postitem.comments_count != undefined">{{ postitem.comments_count }}</label>
|
||||
<label class="text-sm m-1 text-weiss" v-else>Comments disabled</label>
|
||||
</div>
|
||||
<div class="sm:flex"><!-- POST FOOTER -->
|
||||
<div class="flex">
|
||||
<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="postitem.comments != undefined">{{ postitem.comments }}</label>
|
||||
<label class="text-sm m-1 text-weiss" v-else>Comments disabled</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center" @click="addLike(indexus)"> <!-- Likes -->
|
||||
<img alt="" src="../../assets/icons/herz.png" class="align-middle">
|
||||
<label class="text-sm m-1 text-weiss">{{ postitem.likes }}</label>
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
<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>
|
||||
|
||||
<template>
|
||||
<div class="text-grau2 p-3">
|
||||
<div class="text-grau2 p-3 text-center">
|
||||
<a href="">Terms of Service</a>
|
||||
-
|
||||
<a href="">Privacy Policy</a><br>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,43 +1,74 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
// Funktionen um die Seiten zu öffnen
|
||||
// home -> app.vue
|
||||
// PLACEHOLDER
|
||||
import router from "../../router";
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
const sb_home = () => {
|
||||
console.log("home");
|
||||
router.push("/");
|
||||
}
|
||||
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");
|
||||
}
|
||||
let self;
|
||||
const isMobile = ref(false);
|
||||
const show = ref(false);
|
||||
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="pt-4 border-b-2 border-b-grau2">
|
||||
<div class="items-center flex justify-center">
|
||||
<img src="../../assets/esp-logo_no_text.png" alt="" class="rounded-lg h-12 w-24 mx-auto" @click="sb_home">
|
||||
<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 hover:shadow-2xl hover:shadow-grau-dunkel" @click="router.push('/')">
|
||||
</div>
|
||||
<div class="align-middle space-y-3 pt-3 pl-3 pb-4 font-bold text-xl">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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-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-blau shadow-2xl rounded-lg" @click="sb_settings"><img class="pr-2" src="../../assets/icons/zahnrad.png" alt="">Settings</label>
|
||||
<div class="sm:hidden text-weiss items-center flex justify-center mt-2 py-4 border-y-2 border-y-grau2 " v-if="isMobile">
|
||||
<button @click="toggleElements"> {{ show ? 'close navbar' : 'expand navbar' }} </button>
|
||||
</div>
|
||||
<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-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-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-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>
|
||||
</template>
|
||||
|
||||
112
src/components/home_components/popup_chat.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// PLACEHOLDER
|
||||
const hashtags = [
|
||||
{id: 1,name: "xbox", nr_posts: 553,category: "Gaming" } ,
|
||||
{id: 2,name: "lol" , nr_posts: 16422, category: "Gaming"},
|
||||
|
||||
@@ -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>
|
||||
|
||||
<template>
|
||||
<div class="px-20 border-x border-x-grau2 pb-35p">
|
||||
<div class="text-3xl pt-32">
|
||||
<p class="text-weiss text-center">Welcome to <span class="text-button-farbe">ESP</span>!</p>
|
||||
<p class="text-weiss text-center">Login or create a new Account to continue</p>
|
||||
<div class="px-20 sm:border-x sm:border-x-grau2 pb-32">
|
||||
<div class="text-3xl 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">Login to continue</p>
|
||||
</div>
|
||||
<div class="px-20 pt-7">
|
||||
<form class="flex flex-col items-center">
|
||||
<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 w-full max-w-xs bg-grau-dunkel p-4 text-weiss placeholder-grau2 focus:outline-none rounded-lg" type="text" placeholder="Password"><br>
|
||||
<button class="m-4 bg-grau-dunkel w-full max-w-xs p-4 text-weiss rounded-lg">Login</button>
|
||||
<div class="px-20 pt-7"><!-- FORM --->
|
||||
<form class="flex flex-col items-center" @submit.prevent="login">
|
||||
<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 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-button-farbe w-full md:max-w-xs p-4 text-schwarz rounded-lg">Login</button>
|
||||
</form>
|
||||
</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>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
27
src/components/messages.vue
Normal 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>
|
||||
60
src/components/messages_components/msg_main.vue
Normal 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>
|
||||
26
src/components/notifications.vue
Normal 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>
|
||||
124
src/components/notifications_components/notifi_main.vue
Normal 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
@@ -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>
|
||||
|
||||
26
src/components/profile.vue
Normal 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>
|
||||
205
src/components/profile_components/profile_main.vue
Normal 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>
|
||||
25
src/components/register.vue
Normal 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>
|
||||
78
src/components/register_components/register_main.vue
Normal 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
@@ -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>
|
||||
221
src/components/search_components/search_main.vue
Normal 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>
|
||||
35
src/components/settings.vue
Normal 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>
|
||||
@@ -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>
|
||||
37
src/components/settings_components/sa_delete_account.vue
Normal 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>
|
||||
39
src/components/settings_components/sa_reset_password.vue
Normal 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>
|
||||
25
src/components/settings_components/settings_account.vue
Normal 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>
|
||||
32
src/components/settings_components/settings_account_main.vue
Normal 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>
|
||||
77
src/components/settings_components/settings_main.vue
Normal 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>
|
||||
26
src/components/settings_components/settings_sidebar.vue
Normal 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>
|
||||
23
src/components/settings_components/so_main.vue
Normal 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>
|
||||
17
src/components/settings_components/sps_main.vue
Normal 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>
|
||||
@@ -1,10 +1,7 @@
|
||||
<script setup>
|
||||
import Navigationbar from "./home_components/navigationbar.vue";
|
||||
import Feed from "./home_components/feed.vue";
|
||||
import Contacts from "./home_components/contacts.vue";
|
||||
import Legal from "./home_components/legal.vue";
|
||||
import Trending from "./home_components/trending.vue";
|
||||
import QuickSearch from "./home_components/quick_search.vue";
|
||||
</script>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<div id="right">
|
||||
<quick-search> </quick-search>
|
||||
<contacts></contacts>
|
||||
<legal></legal>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import './assets/main.css';
|
||||
import { createApp } from 'vue';
|
||||
import {createApp, onMounted} from 'vue';
|
||||
// @ts-ignore:
|
||||
import App from '../src/App.vue';
|
||||
// @ts-ignore:
|
||||
@@ -7,4 +7,4 @@ import router from "./router/index.ts";
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.mount('#app');
|
||||
app.mount('#app');
|
||||
|
||||
@@ -1,35 +1,90 @@
|
||||
// File: `src/router/index.ts`
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
// 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 Home from "../../src/components/Home.vue";
|
||||
import Login from "../components/Login.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 = [
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: Home,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
name: "login",
|
||||
component: Login,
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
path: "/wip",
|
||||
name: "Work in Progress",
|
||||
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({
|
||||
history: createWebHistory("/"),
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
||||
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;
|
||||
@@ -21,7 +21,8 @@ export default {
|
||||
'35p': '30.825%',
|
||||
}
|
||||
},
|
||||
colors: {
|
||||
plugins: ['tailwind-scrollbar', 'tailwind-scrollbar-hide'],
|
||||
colors: {
|
||||
'logo-farbe-lila': '#5500a2',
|
||||
'logo-farbe-rot': '#a2002b',
|
||||
'logo-farbe-blau': '#0b1074',
|
||||
|
||||