From 652e75147e941e97dfa000b4372e68bb035e0f88 Mon Sep 17 00:00:00 2001 From: Nadav Tasher Date: Sat, 13 Apr 2024 21:59:13 +0300 Subject: [PATCH] Wall example --- .../001-simple-clicker/application/Dockerfile | 2 +- .../002-simple-wall/application/Dockerfile | 9 +++ .../configurations/entrypoint.conf | 2 + .../application/configurations/nginx.conf | 0 .../application/src/backend/app.py | 58 +++++++++++++++ .../application/src/backend/globals.py | 11 +++ .../application/src/backend/worker.py | 24 +++++++ .../src/frontend/application/application.css | 28 ++++++++ .../src/frontend/application/application.js | 40 +++++++++++ .../application/src/frontend/index.html | 71 +++++++++++++++++++ examples/002-simple-wall/docker-compose.yaml | 16 +++++ 11 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 examples/002-simple-wall/application/Dockerfile create mode 100644 examples/002-simple-wall/application/configurations/entrypoint.conf create mode 100644 examples/002-simple-wall/application/configurations/nginx.conf create mode 100644 examples/002-simple-wall/application/src/backend/app.py create mode 100644 examples/002-simple-wall/application/src/backend/globals.py create mode 100644 examples/002-simple-wall/application/src/backend/worker.py create mode 100644 examples/002-simple-wall/application/src/frontend/application/application.css create mode 100644 examples/002-simple-wall/application/src/frontend/application/application.js create mode 100644 examples/002-simple-wall/application/src/frontend/index.html create mode 100644 examples/002-simple-wall/docker-compose.yaml diff --git a/examples/001-simple-clicker/application/Dockerfile b/examples/001-simple-clicker/application/Dockerfile index df45181..8d16c37 100644 --- a/examples/001-simple-clicker/application/Dockerfile +++ b/examples/001-simple-clicker/application/Dockerfile @@ -1,5 +1,5 @@ # Select the base image -FROM webhood/3.8 +FROM webhood/3.8:2024.03.31 # Copy configurations COPY configurations/nginx.conf /etc/nginx/conf.d/nginx.conf diff --git a/examples/002-simple-wall/application/Dockerfile b/examples/002-simple-wall/application/Dockerfile new file mode 100644 index 0000000..bfa1f4a --- /dev/null +++ b/examples/002-simple-wall/application/Dockerfile @@ -0,0 +1,9 @@ +# Select the base image +FROM webhood/3.8:2024.04.13 + +# Copy configurations +COPY configurations/nginx.conf /etc/nginx/conf.d/nginx.conf +COPY configurations/entrypoint.conf /etc/entrypoint/conf.d/entrypoint.conf + +# Copy sources +COPY src /application \ No newline at end of file diff --git a/examples/002-simple-wall/application/configurations/entrypoint.conf b/examples/002-simple-wall/application/configurations/entrypoint.conf new file mode 100644 index 0000000..760e177 --- /dev/null +++ b/examples/002-simple-wall/application/configurations/entrypoint.conf @@ -0,0 +1,2 @@ +[worker] +replication=1 diff --git a/examples/002-simple-wall/application/configurations/nginx.conf b/examples/002-simple-wall/application/configurations/nginx.conf new file mode 100644 index 0000000..e69de29 diff --git a/examples/002-simple-wall/application/src/backend/app.py b/examples/002-simple-wall/application/src/backend/app.py new file mode 100644 index 0000000..f6c1697 --- /dev/null +++ b/examples/002-simple-wall/application/src/backend/app.py @@ -0,0 +1,58 @@ +import time +import asyncio + +# Import utilities +from fsdicts import * +from runtypes import * +from guardify import * + +# Import the router +from router import router, initialize + +# Import globals +from globals import DATABASE, CHANNEL + + +@router.post("/api/post", type_message=Text) +async def post_request(message): + # Make sure message is not too long + assert 4 < len(message) < 60, "Message length is invalid" + + # Create the timestamp + timestamp = time.time() + + # Create the message + DATABASE[timestamp] = message + + +@router.get("/api/posts") +@router.post("/api/posts") +def posts_request(): + # Return the sorted posts + return [(timestamp, DATABASE[timestamp]) for timestamp in sorted(DATABASE)] + + +@router.socket("/socket/posts") +async def posts_socket(websocket): + # Known timestamps + known_timestamps = set(DATABASE) + + # Loop forever + while websocket: + # Check whether there are new messages + current_timestamps = set(DATABASE) + + # Are there new timestamps? + for timestamp in sorted(current_timestamps - known_timestamps): + # Send the new message + await websocket.send_json([timestamp, DATABASE[timestamp]]) + + # Update the timestamps + known_timestamps |= current_timestamps + + # Sleep for one seconds + await asyncio.sleep(1) + + +# Initialize the application +app = initialize() diff --git a/examples/002-simple-wall/application/src/backend/globals.py b/examples/002-simple-wall/application/src/backend/globals.py new file mode 100644 index 0000000..dcbc721 --- /dev/null +++ b/examples/002-simple-wall/application/src/backend/globals.py @@ -0,0 +1,11 @@ +# Import database class +from fsdicts import localdict + +# Create post database +DATABASE = localdict("/opt/database") + +# Cross-worker notifier +CHANNEL = "wall" + +# Maximum age for a post +MAX_AGE_SECONDS = 120 diff --git a/examples/002-simple-wall/application/src/backend/worker.py b/examples/002-simple-wall/application/src/backend/worker.py new file mode 100644 index 0000000..f3410e3 --- /dev/null +++ b/examples/002-simple-wall/application/src/backend/worker.py @@ -0,0 +1,24 @@ +import time +import logging +import threading + +from globals import DATABASE, MAX_AGE_SECONDS + +# Setup the logging +logging.basicConfig(level=logging.INFO, format="[%(asctime)s] [%(process)d] [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S %z") + +# Create stop event +EVENT = threading.Event() + +# Loop until finished +while not EVENT.is_set(): + # Log wait + logging.info("Deleting posts older then %d seconds", MAX_AGE_SECONDS) + + # Loop over posts and delete old posts + for timestamp, message in DATABASE.items(): + if time.time() - timestamp > MAX_AGE_SECONDS: + del DATABASE[timestamp] + + # Wait for event + EVENT.wait(60) diff --git a/examples/002-simple-wall/application/src/frontend/application/application.css b/examples/002-simple-wall/application/src/frontend/application/application.css new file mode 100644 index 0000000..cff2032 --- /dev/null +++ b/examples/002-simple-wall/application/src/frontend/application/application.css @@ -0,0 +1,28 @@ +@media (prefers-color-scheme: dark) { + :root { + --text: #ffffff; + --theme: #0f172a; + --active: #7674af; + --passive: #1e293b; + } +} + +@media (prefers-color-scheme: light) { + :root { + --text: #707070; + --theme: #ffffff; + --active: #c0c0c0; + --passive: #f0f0f0; + } +} + +body { + max-width: 500px; +} + +#wall { + min-height: 80vh; + max-height: 80vh; + overflow-y: scroll; + justify-content: flex-start; +} \ No newline at end of file diff --git a/examples/002-simple-wall/application/src/frontend/application/application.js b/examples/002-simple-wall/application/src/frontend/application/application.js new file mode 100644 index 0000000..da4aa17 --- /dev/null +++ b/examples/002-simple-wall/application/src/frontend/application/application.js @@ -0,0 +1,40 @@ +window.addEventListener("load", async function () { + // Load existing posts + const currentPosts = await call("/api/posts"); + + // Append all posts + for (const post of currentPosts) { + insertPost(post); + } + + // Set the click listener + $("#post").addEventListener("click", async () => { + try { + // Post the message + await progressScreen("Posting message", call("/api/post", { message: $("#message").read() })); + } catch (error) { + // Display error + await alertDialog(error); + } + }); + + // Create the websocket + socket("/socket/posts", (message) => { + // Insert the post + insertPost(JSON.parse(message)); + + // Scroll the messages + $("#wall").scrollTo(0, $("#wall").scrollHeight); + }); +}); + +function insertPost(post) { + // Untuple the post + const [timestamp, message] = post; + + // Create the post element + const postElement = $("#item").populate({ timestamp: timestamp, message: message }); + + // Append the element to the page + $("#wall").appendChild(postElement); +} diff --git a/examples/002-simple-wall/application/src/frontend/index.html b/examples/002-simple-wall/application/src/frontend/index.html new file mode 100644 index 0000000..05a73c7 --- /dev/null +++ b/examples/002-simple-wall/application/src/frontend/index.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Simple Wall + + + + + + + + + + +
+ + +
+ + +
+ + \ No newline at end of file diff --git a/examples/002-simple-wall/docker-compose.yaml b/examples/002-simple-wall/docker-compose.yaml new file mode 100644 index 0000000..ccde772 --- /dev/null +++ b/examples/002-simple-wall/docker-compose.yaml @@ -0,0 +1,16 @@ +version: "3" + +# Configure applcation service +services: + application: + build: application + restart: unless-stopped + ports: + - 80:80 + - 443:443 + volumes: + - data:/opt + +# Configure persistent data volume +volumes: + data: \ No newline at end of file