Mercurial
changeset 131:b230a743a01e
Added blog.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 09 Jan 2026 07:42:04 -0800 |
| parents | f7860f491a8c (diff) 3a564ffb2092 (current diff) |
| children | 7a63e41a21fb |
| files | mrjunejune/src/blog/websocket-demystified/index.md mrjunejune/src/public/web-socket-header.png |
| diffstat | 54 files changed, 1367 insertions(+), 2572 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.bazelrc Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,5 @@ +# Auto-select config based on host OS +common --enable_platform_specific_config + +# Suppress duplicate library warnings from openssl BCR module on macOS +build:macos --linkopt=-Wl,-no_warn_duplicate_libraries
--- a/gara/BUILD Fri Jan 09 07:19:09 2026 -0800 +++ b/gara/BUILD Fri Jan 09 07:42:04 2026 -0800 @@ -3,7 +3,7 @@ cc_binary( name = "gara_c", srcs = ["main.c"], - deps = ["//seobeo:seobeo"], + deps = ["//seobeo:seobeo_min"], )
--- a/gui_ze/gui_ze.bzl Fri Jan 09 07:19:09 2026 -0800 +++ b/gui_ze/gui_ze.bzl Fri Jan 09 07:42:04 2026 -0800 @@ -32,7 +32,6 @@ for directory in f.path.split("/"): if directory == binary.short_path.split("/")[0]: break - print("\n\n equals: ", directory, binary.short_path.split("/")[0]); start += 1 # Remove the first folder (output) and last file (actaul files that needed to be copied) @@ -49,9 +48,6 @@ command = " && ".join(copy_cmd), progress_message = "Bundling {}".format(ctx.label.name), ) - - print("[INFO] See {}".format(out_dir.path)) - return [DefaultInfo(files = depset([out_dir]))] bundle = rule(
--- a/hg-web/BUILD Fri Jan 09 07:19:09 2026 -0800 +++ b/hg-web/BUILD Fri Jan 09 07:42:04 2026 -0800 @@ -26,11 +26,3 @@ name = "hg_web_server_bundle", binary = ":hg_web_server", ) - -cc_binary( - name = "hg_web_server_dev", - srcs = ["main.c"], - deps = ["//seobeo:seobeo_server_dev"], - data = [":src_files"], - defines = ["REPO_ROOT=\\\"\"/Users/mrjunejune/zenbu\"\\\""], -)
--- a/mrjunejune/BUILD Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/BUILD Fri Jan 09 07:42:04 2026 -0800 @@ -1,8 +1,8 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_library.bzl", "cc_library") -# load("@rules_python//python:py_binary.bzl", "py_binary") load("//gui_ze:gui_ze.bzl", "move_files_into_dir", "bundle") +# Files move_files_into_dir( name = "compiled_ts_games", srcs = [ @@ -14,7 +14,6 @@ move_files_into_dir( name = "compiled_ts", srcs = [ - "//playground:hello", "//markdown_converter:markdown_to_html", ], dest = "src", @@ -23,22 +22,26 @@ filegroup( name = "src_files", srcs = glob(["src/**"]) + [":compiled_ts", ":compiled_ts_games"], + visibility = ["//mrjunejune/test:__pkg__"], ) +# Server binary cc_binary( name = "mrjunejune_server", srcs = ["main.c"], - deps = ["//seobeo:seobeo_server"], + deps = ["//seobeo:seobeo_tcp_server_ws"], data = [":src_files"], + visibility = ["//mrjunejune/test:__pkg__"], ) cc_binary( name = "mrjunejune_server_dev", srcs = ["main.c"], - deps = ["//seobeo:seobeo_server_dev"], + deps = ["//seobeo:seobeo_tcp_server_ws"], data = [":src_files"], ) +# Rlease bundle bundle( name = "mrjunejune_server_bundle", binary = ":mrjunejune_server", @@ -49,45 +52,8 @@ binary = ":mrjunejune_server_dev", ) -cc_test( - name = "integration_test", - srcs = ["test/integration_test.c"], - deps = ["//seobeo:seobeo_client"], - data = [ - "//mrjunejune:mrjunejune_server", - "//mrjunejune:src_files", - "//mrjunejune:test_snapshots", - "//mrjunejune:test_files", - ], - size = "large", - timeout = "long", - args = ["$(location //mrjunejune:mrjunejune_server)"], -) - -cc_binary( - name = "create_snapshots", - srcs = ["test/create_snapshots.c"], - deps = ["//seobeo:seobeo_client"], - data = [ - "//mrjunejune:mrjunejune_server", - "//mrjunejune:src_files", - ], - args = ["$(location //mrjunejune:mrjunejune_server)"], -) - -filegroup( - name = "test_snapshots", - srcs = glob(["test/snapshots/**"]), -) - -filegroup( - name = "test_files", - srcs = [ - "test/shiba.webp", - "test/test_avi.avi", - ], -) - +# Experimenting with python to see if I can call it as ffi. +# load("@rules_python//python:py_binary.bzl", "py_binary") # This was to use python ffi, but w/e # cc_library( # name = "mrjunejune_server_lib",
--- a/mrjunejune/main.c Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/main.c Fri Jan 09 07:42:04 2026 -0800 @@ -1,3 +1,4 @@ +#define SEOBEO_ENABLE_DEBUG #include "seobeo/seobeo.h" #include <time.h> @@ -473,16 +474,40 @@ return resp; } +void Chat_Handler(Seobeo_WebSocket_Server_Connection *p_conn, Seobeo_WebSocket_Message *p_msg, void *p_user_data) +{ + (void)p_user_data; + + if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT) + { + char message[2048]; + snprintf(message, sizeof(message), "[%s]: %.*s", p_conn->client_id, (int)p_msg->length, (char*)p_msg->data); + + Seobeo_Log(SEOBEO_INFO, "[Chat] Broadcasting: %s\n", message); + Seobeo_WebSocket_Server_Broadcast_Text(message, p_conn); + } +} + +Seobeo_Request_Entry *GetTalk(Seobeo_Request_Entry *req, Dowa_Arena *arena) +{ + Seobeo_Request_Entry *resp = NULL; + char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); + Seobeo_ServerSideRender(final_body, "/talk/index.html", arena); + Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); + return resp; +} CREATE_REDIRECT_HANDLER(HomePage, "/") CREATE_REDIRECT_HANDLER(Resume, "/resume") CREATE_REDIRECT_HANDLER(Tools, "/tools") CREATE_REDIRECT_HANDLER(MarkDownToHtml, "/tools/markdown_to_html") CREATE_REDIRECT_HANDLER(FileConverter, "/tools/file_converter") +CREATE_REDIRECT_HANDLER(Talk, "/talk") int main(void) { Seobeo_Router_Init(); + Seobeo_Router_Register("GET", "/", GetHomePage); Seobeo_Router_Register("GET", "/index.html", GetRedirectHomePage); @@ -507,5 +532,15 @@ Seobeo_Router_Register("GET", "/blog", RenderBlogList); Seobeo_Router_Register("GET", "/blog/:blog_id", RenderBlog); + // -- Talk --/ + Seobeo_Router_Register("GET", "/talk", GetTalk); + // TODO: Bug where I can't est redriect huh... + Seobeo_Router_Register("GET", "/talk/index.html", GetRedirectTalk); + + printf("Registered Websockets\n"); + + Seobeo_WebSocket_Server_Init(); + Seobeo_WebSocket_Server_Register("/chat", Chat_Handler, NULL); + Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 3); }
--- a/mrjunejune/python_server.py Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -from cffi import FFI -import os - -ffi = FFI() -ffi.cdef("void start_server(void);") - -# Bazel runs binaries from a sandbox, so use runfiles to locate the .so -import pathlib -runfiles_dir = pathlib.Path(__file__).parent -libpath = runfiles_dir / "mrjunejune_server_so.so" - -C = ffi.dlopen(str(libpath)) -C.start_server() -
--- a/mrjunejune/server_entry.c Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -#include "seobeo/seobeo.h" - -void start_server(void) { - Seobeo_Web_Server_Start("mrjunejune/pages", "6969", SEOBEO_MODE_EDGE, 2); -}
--- a/mrjunejune/src/blog/websocket-demystified/index.md Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/src/blog/websocket-demystified/index.md Fri Jan 09 07:42:04 2026 -0800 @@ -31,9 +31,11 @@ To start the upgrade from HTTP to WebSocket, the client sends a standard GET request but with some very specific headers. +<div class="center"> <img src="/public/white-noise-grass.png" /> </div> + I’m assuming you know how HTTP works. If not, you can open a developer tool by right clicking on your browser and seeing into network tab and refershign the page. The only interesting values here is the `Sec-WebSocket-Key`. This key is usually a 16-byte random value encoded in **Base64**. -> **Note:** It’s not for security—it’s to prevent intermediate caches from accidentally serving a cached WebSocket response to a different client. +**Note:** It’s not for security—it’s to prevent intermediate caches from accidentally serving a cached WebSocket response to a different client. But before we jump into that, we need to construct that Base64 key. @@ -43,6 +45,7 @@ > "Base64 is a binary-to-text encoding scheme that represents data in an ASCII string format by translating it into a radix-64 representation, using a specific set of 64 printable characters." — Gemini + Nice, it didn't halluciante. Let's constracut these. Here they are 64 characters that are safe to print in ASCII.: ```c
--- a/mrjunejune/src/parts/base_head.html Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/src/parts/base_head.html Fri Jan 09 07:42:04 2026 -0800 @@ -5,10 +5,10 @@ <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> +<!-- <link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> --> +<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> --> -<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> +<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> <link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/src/talk/index.html Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Talk!</title> + {{/parts/base_head.html}} + <style> + body { font-family: sans-serif; padding: 20px; } + #messages { height: 200px; border: 1px solid #ccc; overflow-y: scroll; margin-bottom: 10px; padding: 10px; } + #chat { display: flex; gap: 10px; } + input { flex-grow: 1; } + </style> +</head> +<body> + {{/parts/header.html}} + <h1>Talks</h1> + + <div id="messages"></div> + + <div id="chat"> + <input type="text" id="messageInput" placeholder="Type a message..."> + <button id="sendBtn">Send</button> + </div> + {{/parts/footer.html}} + <script> + const ws = new WebSocket('ws://localhost:6969/echo'); + const messagesDiv = document.getElementById('messages'); + + ws.onopen = () => { + console.log('Connected!'); + appendMessage('System: Connected to server'); + }; + + ws.onmessage = (event) => { + console.log('Received:', event.data); + appendMessage('Server: ' + event.data); + }; + + // Function to send message + sendBtn.onclick = () => { + const message = messageInput.value; + if (message) { + ws.send(message); + appendMessage('You: ' + message); + messageInput.value = ''; // Clear input + } + }; + + // Helper to show messages on screen + function appendMessage(text) { + const msg = document.createElement('p'); + msg.textContent = text; + messagesDiv.appendChild(msg); + messagesDiv.scrollTop = messagesDiv.scrollHeight; + } + + messageInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter' && !event.shiftKey) + { + event.preventDefault(); + sendBtn.click(); + } + }); + </script> +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/test/BUILD Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,48 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_test.bzl", "cc_test") +load("//gui_ze:gui_ze.bzl", "move_files_into_dir", "bundle") + +# Files needed for test +filegroup( + name = "test_snapshots", + srcs = glob(["snapshots/**"]), +) + +filegroup( + name = "test_files", + srcs = [ + "shiba.webp", + "test_avi.avi", + ], +) + +# To create a snapsho to compare +cc_binary( + name = "create_snapshots", + srcs = ["auto_generated_test.c", "test.h"], + deps = ["//seobeo:seobeo_tcp_client"], + data = [ + "//mrjunejune:mrjunejune_server", + "//mrjunejune:src_files", + ], + args = ["$(location //mrjunejune:mrjunejune_server)"], +) + +# Tests +cc_test( + name = "integration_test", + srcs = [ + "integration_test.c", + "test.h" + ], + deps = ["//seobeo:seobeo_tcp_client"], + data = [ + "//mrjunejune:mrjunejune_server", + "//mrjunejune:src_files", + ":test_snapshots", + ":test_files", + ], + size = "large", + timeout = "long", + args = ["$(location //mrjunejune:mrjunejune_server)"], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/test/auto_generated_test.c Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,45 @@ +// Auto-generated test file +// Source: /Users/mrjunejune/zenbu/mrjunejune/main.c +// DO NOT EDIT - Regenerate with: bazel run //seobeo:test_generator + +#include "mrjunejune/test/test.h" + +#define TEST_HOST "127.0.0.1" +#define TEST_PORT "6969" + +int main(int argc, char *argv[]) +{ + printf("=== Auto-generated Snapshot Creator ===\n\n"); + + const char *server_binary = "./server"; + if (argc > 1) server_binary = argv[1]; + + pid_t server_pid = start_test_server(server_binary); + if (server_pid < 0) return 1; + + SnapshotConfig configs[] = { + {"/", 200, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/index.html", 301, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/resume", 200, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/resume/index.html", 301, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/tools", 200, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/tools/index.html", 301, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/tools/markdown_to_html", 200, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/tools/markdown_to_html/index.html", 301, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/tools/file_converter", 200, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/tools/file_converter/index.html", 301, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + // TODO: POST route - POST /api/convert/image-to-webp - requires request body + // TODO: POST route - POST /api/convert/video-to-mp4 - requires request body + // TODO: Dynamic route - GET /api/download/:filename - fill in actual path + {"/blog", 200, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + // TODO: Dynamic route - GET /blog/:blog_id - fill in actual path + {"/talk", 200, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + {"/talk/index.html", 301, SNAPSHOT_DIR, TEST_HOST, TEST_PORT}, + }; + + int count = sizeof(configs) / sizeof(configs[0]); + int result = Seobeo_Snapshots_Create_Batch(configs, count); + + stop_test_server(server_pid); + return result; +}
--- a/mrjunejune/test/create_snapshots.c Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,199 +0,0 @@ -#include "seobeo/seobeo.h" -#include "seobeo/snapshot_creator.h" -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <sys/wait.h> -#include <signal.h> - -#define TEST_PORT "6969" -#define TEST_HOST "127.0.0.1" -#define SNAPSHOT_DIR "mrjunejune/test/snapshots" - -// Start the server process -static pid_t start_server(const char *server_binary) -{ - pid_t server_pid = fork(); - - if (server_pid < 0) - { - perror("fork"); - return -1; - } - - if (server_pid == 0) - { - // Child process - run server - printf("Starting server on port %s...\n", TEST_PORT); - execl(server_binary, server_binary, NULL); - perror("execl failed"); - exit(1); - } - - // Parent - verify server started - printf("Server started (PID: %d)\n", server_pid); - - usleep(100000); - int status; - pid_t result = waitpid(server_pid, &status, WNOHANG); - if (result != 0) - { - if (WIFEXITED(status)) - { - fprintf(stderr, "Server exited with code: %d\n", WEXITSTATUS(status)); - } - else if (WIFSIGNALED(status)) - { - fprintf(stderr, "Server killed by signal: %d\n", WTERMSIG(status)); - } - return -1; - } - - sleep(2); - printf("Server ready\n\n"); - - return server_pid; -} - -// Stop the server process -static void stop_server_test(pid_t server_pid) -{ - if (server_pid > 0) - { - printf("\nStopping server (PID: %d)...\n", server_pid); - kill(server_pid, SIGTERM); - waitpid(server_pid, NULL, 0); - printf("Server stopped\n"); - } -} - -int main(int argc, char *argv[]) -{ - printf("=== Seobeo Snapshot Creator ===\n\n"); - - // Get workspace directory (where the source files are) - const char *workspace_dir = getenv("BUILD_WORKSPACE_DIRECTORY"); - if (!workspace_dir) - { - fprintf(stderr, "Error: BUILD_WORKSPACE_DIRECTORY not set\n"); - fprintf(stderr, "This binary must be run with 'bazel run'\n"); - return 1; - } - - // Construct full path to snapshot directory - char snapshot_path[1024]; - snprintf(snapshot_path, sizeof(snapshot_path), "%s/%s", workspace_dir, SNAPSHOT_DIR); - - // Get server binary path - const char *server_binary = "./mrjunejune_server"; - if (argc > 1) - { - server_binary = argv[1]; - } - - printf("Workspace: %s\n", workspace_dir); - printf("Server binary: %s\n", server_binary); - printf("Snapshot directory: %s\n\n", snapshot_path); - - // Start server - pid_t server_pid = start_server(server_binary); - if (server_pid < 0) - { - fprintf(stderr, "Failed to start server\n"); - return 1; - } - - // Define snapshots to create - paths that should succeed (200 OK) - SnapshotConfig success_snapshots[] = { - {"/", 200, snapshot_path, TEST_HOST, TEST_PORT}, - {"/resume", 200, snapshot_path, TEST_HOST, TEST_PORT}, - {"/tools", 200, snapshot_path, TEST_HOST, TEST_PORT}, - {"/tools/markdown_to_html", 200, snapshot_path, TEST_HOST, TEST_PORT}, - {"/tools/file_converter", 200, snapshot_path, TEST_HOST, TEST_PORT}, - }; - int num_success = sizeof(success_snapshots) / sizeof(success_snapshots[0]); - - // Define snapshots for redirect endpoints (301) - SnapshotConfig redirect_snapshots[] = { - {"/index.html", 301, snapshot_path, TEST_HOST, TEST_PORT}, - {"/resume/index.html", 301, snapshot_path, TEST_HOST, TEST_PORT}, - {"/tools/index.html", 301, snapshot_path, TEST_HOST, TEST_PORT}, - {"/tools/markdown_to_html/index.html", 301, snapshot_path, TEST_HOST, TEST_PORT}, - {"/tools/file_converter/index.html", 301, snapshot_path, TEST_HOST, TEST_PORT}, - }; - int num_redirects = sizeof(redirect_snapshots) / sizeof(redirect_snapshots[0]); - - SnapshotConfig error_snapshots[] = { - {"/nonexistent", 404, snapshot_path, TEST_HOST, TEST_PORT}, - {"/does/not/exist", 404, snapshot_path, TEST_HOST, TEST_PORT}, - {"/missing.html", 404, snapshot_path, TEST_HOST, TEST_PORT}, - }; - int num_errors = sizeof(error_snapshots) / sizeof(error_snapshots[0]); - - int total_failed = 0; - int total_passed = 0; - - // Create success snapshots - printf("Creating snapshots for successful paths:\n\n"); - for (int i = 0; i < num_success; i++) - { - if (Seobeo_Snapshot_Create(&success_snapshots[i]) == 0) - { - total_passed++; - } - else - { - total_failed++; - } - } - - // Create redirect snapshots - printf("\nCreating snapshots for redirect paths:\n\n"); - for (int i = 0; i < num_redirects; i++) - { - if (Seobeo_Snapshot_Create(&redirect_snapshots[i]) == 0) - { - total_passed++; - } - else - { - total_failed++; - } - } - - // Create error snapshots - printf("\nCreating snapshots for error paths:\n\n"); - for (int i = 0; i < num_errors; i++) - { - if (Seobeo_Snapshot_Create(&error_snapshots[i]) == 0) - { - total_passed++; - } - else - { - total_failed++; - } - } - - // Stop server - stop_server_test(server_pid); - - // Print summary - printf("\n=== Summary ===\n"); - printf("Snapshots created: %d\n", total_passed); - printf("Failed: %d\n", total_failed); - - if (total_failed == 0) - { - printf("\n✓ All snapshots created successfully!\n"); - printf("\nSnapshots saved to: %s/\n", snapshot_path); - printf("\nRun tests to verify:\n"); - printf(" bazel test //mrjunejune:integration_test\n"); - return 0; - } - else - { - printf("\n✗ Some snapshots failed\n"); - return 1; - } -}
--- a/mrjunejune/test/integration_test.c Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/integration_test.c Fri Jan 09 07:42:04 2026 -0800 @@ -1,19 +1,4 @@ -#include "seobeo/seobeo.h" -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/wait.h> -#include <signal.h> -#include <assert.h> - -#define TEST_PORT "6969" -#define TEST_HOST "127.0.0.1" -#define MAX_RESPONSE_SIZE (1024 * 1024) -#ifndef SNAPSHOT_DIR - // TODO: Make it as current directory /snapshots... - #define SNAPSHOT_DIR "mrjunejune/test/snapshots" -#endif +#include "mrjunejune/test/test.h" // Test case structure typedef struct { @@ -25,6 +10,37 @@ size_t response_len; } TestCase; +void debug_diff(const char *expected, const char *actual) +{ + printf("\n--- DIFF (Expected vs Actual) ---\n"); + + // Create copies to use with strtok (strtok modifies the string) + char *exp_copy = strdup(expected); + char *act_copy = strdup(actual); + + char *exp_line = strtok(exp_copy, "\n"); + char *act_line = strtok(act_copy, "\n"); + + int line_num = 1; + while (exp_line != NULL || act_line != NULL) { + if (exp_line && act_line && strcmp(exp_line, act_line) == 0) { + // Lines match - optional: print nothing or a dot + } else { + printf("Line %d mismatch:\n", line_num); + printf(" EXP: [%s]\n", exp_line ? exp_line : "(end of string)"); + printf(" ACT: [%s]\n", act_line ? act_line : "(end of string)"); + printf(" -----------------------------------\n"); + } + + exp_line = strtok(NULL, "\n"); + act_line = strtok(NULL, "\n"); + line_num++; + } + + free(exp_copy); + free(act_copy); +} + // Helper: Convert URL path to filename // "/" -> "root.snapshot" // "/index.html" -> "index.html.snapshot" @@ -117,274 +133,68 @@ return 0; } -// Helper: Create test client -Seobeo_Handle* create_test_client() -{ - Seobeo_Handle *client = Seobeo_Stream_Handle_Client_Create(TEST_HOST, TEST_PORT, FALSE); - if (!client || client->socket < 0) - { - if (client) - { - Seobeo_Handle_Destroy(client); - } - return NULL; - } - return client; -} - -// Helper: Generate default HTTP GET request -int generate_http_get_request(char *buffer, size_t buffer_size, const char *path) +int execute_test_case(TestCase *test, pid_t server_pid) { - return snprintf( - buffer, buffer_size, - "GET %s HTTP/1.1\r\n" - "Host: %s\r\n" - "Connection: close\r\n" - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" - "User-Agent: SeobeoTestClient/1.0\r\n" - "\r\n", - path, TEST_HOST - ); -} - -// Helper: Send HTTP request -int send_http_request(Seobeo_Handle *client, const char *path, const char *custom_request) -{ - char request_buffer[4096]; - int request_len; - - if (custom_request) + printf(" Testing: GET %s (expecting %d)\n", test->path, test->expected_status); + + int32 max_url_length = 1024*3; + char *url = malloc(sizeof(char)*max_url_length); + snprintf(url, max_url_length, "%s%s", TEST_URL, test->path); + Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(url); + if (!p_req) { - request_len = snprintf(request_buffer, sizeof(request_buffer), "%s", custom_request); - } - else - { - request_len = generate_http_get_request(request_buffer, sizeof(request_buffer), path); - } - - if (request_len < 0 || request_len >= sizeof(request_buffer)) - { - fprintf(stderr, "Request buffer too small\n"); - return -1; - } - - Seobeo_Handle_Queue(client, (uint8*)request_buffer, (uint32)request_len); - return Seobeo_Handle_Flush(client); -} - -// Helper: Read HTTP response -int read_http_response(Seobeo_Handle *client, char **response_out, size_t *response_len_out) -{ - char *response = malloc(MAX_RESPONSE_SIZE); - if (!response) - { + printf("Can't create requests"); return -1; } - size_t total_bytes = 0; - int attempts = 0; - const int max_attempts = 100; - - while (attempts++ < max_attempts && total_bytes < MAX_RESPONSE_SIZE - 1) + Seobeo_Client_Response *p_resp = Seobeo_Client_Request_Execute(p_req); + if (!p_resp) { - int bytes_read = Seobeo_Handle_Read(client); - - if (bytes_read > 0) - { - size_t to_copy = client->read_buffer_len; - if (total_bytes + to_copy > MAX_RESPONSE_SIZE - 1) - { - to_copy = MAX_RESPONSE_SIZE - 1 - total_bytes; - } - - memcpy(response + total_bytes, client->read_buffer, to_copy); - total_bytes += to_copy; - Seobeo_Handle_Consume(client, (uint32)to_copy); - } - else if (bytes_read == -2) - { - // Connection closed - break; - } - else if (bytes_read == 0) - { - // Would block - usleep(10000); - continue; - } - else - { - free(response); - return -1; - } - } - - response[total_bytes] = '\0'; - *response_out = response; - *response_len_out = total_bytes; - - return (total_bytes > 0) ? 0 : -1; -} - -// Helper: Parse HTTP status code -int parse_http_status(const char *response) -{ - if (!response || strlen(response) < 12) - { - return -1; - } - - const char *status_start = strstr(response, "HTTP/1.1 "); - if (!status_start) - { - status_start = strstr(response, "HTTP/1.0 "); - } - - if (!status_start) - { + printf("No response"); return -1; } - int status_code; - if (sscanf(status_start + 9, "%d", &status_code) == 1) + if (p_resp->status_code != test->expected_status) { - return status_code; - } - - return -1; -} - -// Helper: Check if status is a redirect -int is_redirect_status(int status) -{ - return (status >= 300 && status < 400); -} - -// Helper: Execute a test case -int execute_test_case(TestCase *test, pid_t server_pid) -{ - printf(" Testing: GET %s (expecting %d)\n", test->path, test->expected_status); - - Seobeo_Handle *client = create_test_client(); - if (!client) - { - printf(" ✗ Failed to create client connection\n"); - return -1; - } - - if (send_http_request(client, test->path, NULL) < 0) - { - printf(" ✗ Failed to send request\n"); - Seobeo_Handle_Destroy(client); + printf(" ✗ Status mismatch: expected %d, got %d\n", + test->expected_status, p_resp->status_code); + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); return -1; } - char *response = NULL; - size_t response_len = 0; - if (read_http_response(client, &response, &response_len) < 0) - { - printf(" ✗ Failed to read response\n"); - Seobeo_Handle_Destroy(client); - return -1; - } - - test->actual_response = response; - test->response_len = response_len; + printf(" ✓ Status code: %d\n", p_resp->status_code); - int actual_status = parse_http_status(response); - if (actual_status != test->expected_status) - { - printf(" ✗ Status mismatch: expected %d, got %d\n", - test->expected_status, actual_status); - Seobeo_Handle_Destroy(client); - return -1; - } - - printf(" ✓ Status code: %d\n", actual_status); - - // For redirects, skip content comparison - if (is_redirect_status(actual_status)) - { - printf(" ⚠ Redirect status - skipping content comparison\n"); - Seobeo_Handle_Destroy(client); - return 0; - } - - // Only verify 200 OK responses against snapshots - if (actual_status == 200) + if (p_resp->status_code == 200) { if (!test->expected_content) { printf(" ✗ No expected snapshot found: %s\n", test->expected_file_path); printf(" → Run: bazel run //mrjunejune:create_snapshots\n"); - Seobeo_Handle_Destroy(client); + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); return -1; } - if (strcmp(response, test->expected_content) != 0) + if (strcmp(p_resp->body, test->expected_content) != 0) { printf(" ✗ Response does not match expected snapshot\n"); printf(" Expected file: %s\n", test->expected_file_path); - Seobeo_Handle_Destroy(client); + debug_diff(p_resp->body, test->expected_content); + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); return -1; } - printf(" ✓ Response matches snapshot (%zu bytes)\n", response_len); + printf(" ✓ Response matches snapshot (%zu bytes)\n", p_resp->body_length); } - Seobeo_Handle_Destroy(client); + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); return 0; } -// Helper: Execute custom request test -int execute_custom_request_test(const char *name, const char *custom_request, - int expected_status, pid_t server_pid) -{ - printf(" Testing: %s (expecting %d)\n", name, expected_status); - - Seobeo_Handle *client = create_test_client(); - if (!client) - { - printf(" ✗ Failed to create client connection\n"); - return -1; - } - - if (send_http_request(client, NULL, custom_request) < 0) - { - printf(" ✗ Failed to send request\n"); - Seobeo_Handle_Destroy(client); - return -1; - } - - char *response = NULL; - size_t response_len = 0; - if (read_http_response(client, &response, &response_len) < 0) - { - printf(" ✗ Failed to read response\n"); - Seobeo_Handle_Destroy(client); - return -1; - } - - int actual_status = parse_http_status(response); - if (actual_status != expected_status) - { - printf(" ✗ Status mismatch: expected %d, got %d\n", - expected_status, actual_status); - free(response); - Seobeo_Handle_Destroy(client); - return -1; - } - - printf(" ✓ Status code: %d\n", actual_status); - printf(" ✓ Response received (%zu bytes)\n", response_len); - - free(response); - Seobeo_Handle_Destroy(client); - return 0; -} - -// Helper: Send POST request with file data -int send_post_file(Seobeo_Handle *client, const char *path, const char *file_data, size_t file_size) +int send_post_file(Seobeo_Handle *p_req, const char *path, const char *file_data, size_t file_size) { char request_buffer[8192]; int header_len = snprintf( @@ -405,7 +215,7 @@ } // Send headers - Seobeo_Handle_Queue(client, (uint8*)request_buffer, (uint32)header_len); + Seobeo_Handle_Queue(p_req, (uint8*)request_buffer, (uint32)header_len); // Send file data in chunks if needed size_t remaining = file_size; @@ -413,15 +223,14 @@ while (remaining > 0) { size_t chunk_size = remaining > 4096 ? 4096 : remaining; - Seobeo_Handle_Queue(client, (uint8*)ptr, (uint32)chunk_size); + Seobeo_Handle_Queue(p_req, (uint8*)ptr, (uint32)chunk_size); ptr += chunk_size; remaining -= chunk_size; } - return Seobeo_Handle_Flush(client); + return Seobeo_Handle_Flush(p_req); } -// Helper: Extract JSON field value from response body char* extract_json_field(const char *json, const char *field, char *buffer, size_t buffer_size) { char search_pattern[256]; @@ -452,7 +261,6 @@ return buffer; } -// Helper: Test POST file conversion int test_file_conversion(const char *endpoint, const char *test_file_path, const char *expected_format, pid_t server_pid) { @@ -461,204 +269,64 @@ // Read test file size_t file_size; char *file_data = read_file(test_file_path, &file_size); - if (!file_data) - { + if (!file_data) { printf(" ✗ Failed to read test file: %s\n", test_file_path); return -1; } - printf(" → Loaded test file (%zu bytes)\n", file_size); + char url[1024]; + snprintf(url, sizeof(url), "%s%s", TEST_URL, endpoint); - // Create client and send request - Seobeo_Handle *client = create_test_client(); - if (!client) - { - printf(" ✗ Failed to create client connection\n"); - free(file_data); - return -1; - } + Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(url); + Seobeo_Client_Request_Set_Method(p_req, "POST"); + Seobeo_Client_Request_Set_Body(p_req, (uint8*)file_data, (uint32)file_size); + Seobeo_Client_Request_Add_Header_Array(p_req, "Content-Type: application/octet-stream"); - if (send_post_file(client, endpoint, file_data, file_size) < 0) - { - printf(" ✗ Failed to send POST request\n"); - free(file_data); - Seobeo_Handle_Destroy(client); - return -1; - } - + Seobeo_Client_Response *p_resp = Seobeo_Client_Request_Execute(p_req); free(file_data); - // Read response - char *response = NULL; - size_t response_len = 0; - if (read_http_response(client, &response, &response_len) < 0) - { - printf(" ✗ Failed to read response\n"); - Seobeo_Handle_Destroy(client); - return -1; - } - - Seobeo_Handle_Destroy(client); - - // Parse status - int status = parse_http_status(response); - if (status != 200) - { - printf(" ✗ Conversion failed with status: %d\n", status); - printf(" Response: %s\n", response); - free(response); - return -1; - } - - printf(" ✓ Status code: 200\n"); - - // Extract download URL from JSON response - const char *body = strstr(response, "\r\n\r\n"); - if (!body) - { - printf(" ✗ No response body found\n"); - free(response); - return -1; - } - body += 4; + if (!p_resp || p_resp->status_code != 200) { + printf(" ✗ Conversion failed with status: %d\n", p_resp ? p_resp->status_code : 0); + if (p_resp) Seobeo_Client_Response_Destroy(p_resp); - char download_url[512]; - if (!extract_json_field(body, "download_url", download_url, sizeof(download_url))) - { - printf(" ✗ Failed to extract download_url from response\n"); - printf(" Response body: %s\n", body); - free(response); - return -1; - } - - printf(" ✓ Conversion succeeded\n"); - printf(" ✓ Download URL: %s\n", download_url); - free(response); - - // Test downloading the converted file - printf(" → Testing download: GET %s\n", download_url); - - client = create_test_client(); - if (!client) - { - printf(" ✗ Failed to create client for download\n"); - return -1; - } - - if (send_http_request(client, download_url, NULL) < 0) - { - printf(" ✗ Failed to send download request\n"); - Seobeo_Handle_Destroy(client); - return -1; - } - - response = NULL; - response_len = 0; - if (read_http_response(client, &response, &response_len) < 0) - { - printf(" ✗ Failed to read download response\n"); - Seobeo_Handle_Destroy(client); + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); return -1; } - Seobeo_Handle_Destroy(client); - - status = parse_http_status(response); - if (status != 200) - { - printf(" ✗ Download failed with status: %d\n", status); - free(response); - return -1; - } - - // Find body in download response - body = strstr(response, "\r\n\r\n"); - if (!body) - { - printf(" ✗ No file data in download response\n"); - free(response); - return -1; - } - body += 4; - - size_t downloaded_size = response_len - (body - response); - - // Verify content type in response headers - const char *content_type = strstr(response, "Content-Type: "); - if (!content_type) - { - printf(" ✗ No Content-Type header in download\n"); - free(response); - return -1; - } - - if (strstr(content_type, expected_format) == NULL) - { - printf(" ✗ Wrong content type (expected %s)\n", expected_format); - free(response); + // Extract download URL from JSON body + char download_url_path[512]; + if (!extract_json_field((char*)p_resp->body, "download_url", download_url_path, sizeof(download_url_path))) { + printf(" ✗ Failed to extract download_url\n"); + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); return -1; } - printf(" ✓ Downloaded converted file (%zu bytes)\n", downloaded_size); - printf(" ✓ Content-Type: %s\n", expected_format); - - free(response); - return 0; -} + printf(" ✓ Conversion succeeded. Download URL: %s\n", download_url_path); + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); -// Helper: Start test server -pid_t start_test_server(const char *server_binary) -{ - pid_t server_pid = fork(); + printf(" → Testing download: GET %s\n", download_url_path); + char full_download_url[1024]; + snprintf(full_download_url, sizeof(full_download_url), "%s%s", TEST_URL, download_url_path); + + p_req = Seobeo_Client_Request_Create(full_download_url); + p_resp = Seobeo_Client_Request_Execute(p_req); - if (server_pid < 0) + if (!p_resp || p_resp->status_code != 200) { - perror("fork"); + printf(" ✗ Download failed\n"); + if (p_resp) Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); return -1; } - if (server_pid == 0) - { - printf("Starting server on port %s...\n", TEST_PORT); - execl(server_binary, server_binary, NULL); - perror("execl failed"); - exit(1); - } - - printf("Server started (PID: %d)\n", server_pid); + printf(" ✓ Downloaded converted file (%u bytes)\n", p_resp->body_length); - usleep(100000); - int status; - pid_t result = waitpid(server_pid, &status, WNOHANG); - if (result != 0) - { - if (WIFEXITED(status)) - { - fprintf(stderr, "Server exited immediately with code: %d\n", WEXITSTATUS(status)); - } - else if (WIFSIGNALED(status)) - { - fprintf(stderr, "Server was killed by signal: %d\n", WTERMSIG(status)); - } - return -1; - } - - sleep(2); - printf("Server ready\n\n"); - - return server_pid; -} - -// Helper: Stop test server -void stop_test_server(pid_t server_pid) -{ - if (server_pid > 0) - { - printf("\nStopping server (PID: %d)...\n", server_pid); - kill(server_pid, SIGTERM); - waitpid(server_pid, NULL, 0); - printf("Server stopped\n"); - } + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); + return 0; } // Helper: Initialize test case with snapshot file @@ -694,14 +362,12 @@ // Main integration test int test_server_client_integration(const char *server_binary) { - printf("=== Server-Client Integration Test ===\n"); + printf("=== Server-p_req Integration Test ===\n"); printf("MODE: Verifying Against Snapshots\n\n"); char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) != NULL) - { printf("Working directory: %s\n", cwd); - } if (access(server_binary, X_OK) != 0) { @@ -714,24 +380,21 @@ pid_t server_pid = start_test_server(server_binary); if (server_pid < 0) - { return -1; - } int failed_tests = 0; int passed_tests = 0; - // Define test cases - paths that should succeed (200 OK) TestCase success_tests[] = { {"/", 200, NULL, NULL, NULL, 0}, {"/resume", 200, NULL, NULL, NULL, 0}, {"/tools", 200, NULL, NULL, NULL, 0}, {"/tools/markdown_to_html", 200, NULL, NULL, NULL, 0}, {"/tools/file_converter", 200, NULL, NULL, NULL, 0}, + {"/talk", 200, NULL, NULL, NULL, 0}, }; int num_success_tests = sizeof(success_tests) / sizeof(success_tests[0]); - // Define test cases - paths that should redirect (301) TestCase redirect_tests[] = { {"/index.html", 301, NULL, NULL, NULL, 0}, {"/resume/index.html", 301, NULL, NULL, NULL, 0}, @@ -741,7 +404,6 @@ }; int num_redirect_tests = sizeof(redirect_tests) / sizeof(redirect_tests[0]); - // Define test cases - paths that should fail (404) TestCase failure_tests[] = { {"/nonexistent", 404, NULL, NULL, NULL, 0}, {"/does/not/exist", 404, NULL, NULL, NULL, 0}, @@ -749,134 +411,73 @@ }; int num_failure_tests = sizeof(failure_tests) / sizeof(failure_tests[0]); - // Initialize all test cases for (int i = 0; i < num_success_tests; i++) - { init_test_case(&success_tests[i]); - } + for (int i = 0; i < num_redirect_tests; i++) - { init_test_case(&redirect_tests[i]); - } + for (int i = 0; i < num_failure_tests; i++) - { init_test_case(&failure_tests[i]); - } - // Run success tests printf("Running tests for paths that should succeed:\n"); for (int i = 0; i < num_success_tests; i++) { if (execute_test_case(&success_tests[i], server_pid) == 0) - { passed_tests++; - } else - { failed_tests++; - } } printf("\n"); - // Run redirect tests printf("Running tests for paths that should redirect:\n"); for (int i = 0; i < num_redirect_tests; i++) { if (execute_test_case(&redirect_tests[i], server_pid) == 0) - { passed_tests++; - } else - { failed_tests++; - } - } - - printf("\n"); - - // Run failure tests - printf("Running tests for paths that should fail:\n"); - for (int i = 0; i < num_failure_tests; i++) - { - if (execute_test_case(&failure_tests[i], server_pid) == 0) - { - passed_tests++; - } - else - { - failed_tests++; - } } printf("\n"); - // Test with custom request - printf("Running tests with custom requests:\n"); - char custom_request[4096]; - snprintf(custom_request, sizeof(custom_request), - "GET / HTTP/1.1\r\n" - "Host: %s\r\n" - "Connection: close\r\n" - "X-Custom-Header: TestValue\r\n" - "\r\n", - TEST_HOST); - - if (execute_custom_request_test("Custom headers GET /", custom_request, 200, server_pid) == 0) + printf("Running tests for paths that should fail:\n"); + for (int i = 0; i < num_failure_tests; i++) { - passed_tests++; - } - else - { - failed_tests++; + if (execute_test_case(&failure_tests[i], server_pid) == 0) + passed_tests++; + else + failed_tests++; } printf("\n"); - // Test POST endpoints printf("Running tests for POST conversion endpoints:\n"); - - // Test image-to-webp conversion if (test_file_conversion("/api/convert/image-to-webp", "mrjunejune/test/shiba.webp", "image/webp", server_pid) == 0) - { passed_tests++; - } else - { failed_tests++; - } printf("\n"); - // Test video-to-mp4 conversion if (test_file_conversion("/api/convert/video-to-mp4", "mrjunejune/test/test_avi.avi", "video/mp4", server_pid) == 0) - { passed_tests++; - } else - { failed_tests++; - } - // Cleanup test cases for (int i = 0; i < num_success_tests; i++) - { cleanup_test_case(&success_tests[i]); - } for (int i = 0; i < num_redirect_tests; i++) - { cleanup_test_case(&redirect_tests[i]); - } for (int i = 0; i < num_failure_tests; i++) - { cleanup_test_case(&failure_tests[i]); - } stop_test_server(server_pid); @@ -893,20 +494,14 @@ const char *server_binary = "./mrjunejune_server"; if (argc > 1) - { server_binary = argv[1]; - } int result = test_server_client_integration(server_binary); if (result == 0) - { printf("\n✓ All tests passed!\n"); - } else - { printf("\n✗ Some tests failed\n"); - } return result; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/test/snapshots/blog.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,208 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> + +<link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> +<link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> + +<!-- <link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> --> +<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> --> + +<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> +<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> +<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> + +<link rel="preload" href="/base.css" as="style" /> +<link rel="stylesheet" href="/base.css" /> + + + </head> + <body> + <style> + :root { + --header-background: var(--white); + --header-color: rgb(var(--black)); + --link-hover-accent: var(--awesome); + } + + /* Fixed icon in top left corner */ + #themeToggle { + position: fixed; + top: 20px; + left: 20px; + background: var(--header-background); + display: flex; + align-items: center; + border-radius: 50%; + cursor: pointer; + z-index: 1000; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease; + } + + #themeToggle:hover { + transform: scale(1.05); + } + + /* Professional header */ + header { + margin: auto; + padding: 1.5em 1em; + font-family: "More", sans-serif; + box-shadow: 0 2px 8px rgba(var(--black), 5%); + width: 720px; + max-width: calc(100% - 2em); + text-align: center; + } + + header h1 { + margin: 0; + font-size: 1.8em; + font-weight: 700; + letter-spacing: -0.5px; + } + + header h1 a { + text-decoration: none; + color: var(--header-color); + } + + header h1 a::before { + display: none; + } + + /* Mobile responsiveness */ + @media (max-width: 720px) { + #themeToggle { + top: 15px; + left: 15px; + } + + header { + padding: 1em; + } + + header h1 { + font-size: 1.5em; + } + } + + @media (max-width: 480px) { + #themeToggle { + top: 10px; + left: 10px; + } + + #themeToggle img { + height: 40px; + width: 40px; + } + + header h1 { + font-size: 1.3em; + } + } + + #logo { + width: 300px; + } + + /* 1. DEFINE THE DEFAULTS (Light Mode) */ + :root { + --logo-invert: invert(0); + --epi-grayscale: grayscale(0) brightness(1); + } + + /* 2. MANUAL DARK OVERRIDE */ + html.dark { + --logo-invert: invert(1); + --epi-grayscale: grayscale(1); + } + + /* 3. MANUAL LIGHT OVERRIDE */ + html.light-mode { + --logo-invert: invert(0); + --epi-grayscale: brightness(2.9) grayscale(1); + } + + /* 4. SYSTEM PREFERENCE */ + @media (prefers-color-scheme: dark) { + :root:not(.light-mode) { + --logo-invert: invert(1); + } + } + + /* 5. APPLY TO ELEMENTS */ + #logo { + -webkit-filter: var(--logo-invert); + filter: var(--logo-invert); + transition: filter 0.3s ease; + } + + .epi-logo { + -webkit-filter: var(--epi-grayscale); + filter: var(--epi-grayscale); + transition: filter 0.3s ease; + } +</style> + +<div id="themeToggle"> + <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> +</div> + +<header> + <h1><a href="/">MrJuneJune</a></h1> +</header> +<script src="/index.js"></script> + + + <main> + <h1 class="title" style="margin-bottom: 20px;"> Blogs </h1> + <ul style="list-style: none;"> + <li style="margin-bottom: 20px;"> + <span> January 08 2026 </span> + <p><h4><a href="/blog/websocket-demystified">Websocket Demystified</a></h4></p> + </li> + <li style="margin-bottom: 20px;"> + <span> January 02 2026 </span> + <p><h4><a href="/blog/my-seobeo-journey">Creating Network Library in C</a></h4></p> + </li> + <li style="margin-bottom: 20px;"> + <span> Apr 12 2025 </span> + <p><h4><a href="/blog/wsl2-ssh">WSL2 Cloudtop Setup</a></h4></p> + </li> + <li style="margin-bottom: 20px;"> + <span> Dec 10 2024 </span> + <p><h4><a href="/blog/multithread-in-js">MultiThreading in JS</a></h4></p> + </li> + <li style="margin-bottom: 20px;"> + <span> Nov 23 2024 </span> + <p><h4><a href="/blog/thoughts-on-tdd">My thoughts on TDD</a></h4></p> + </li> + <li style="margin-bottom: 20px;"> + <span> Nov 21 2024 </span> + <p><h4><a href="/blog/optimizing-grass-rendering">Optimizing Random Placement with Colour Noise</a></h4></p> + </li> + <li style="margin-bottom: 20px;"> + <span> Nov 17 2024 </span> + <p><h4><a href="/blog/thoughts-on-ide">My thoughts on IDE</a></h4></p> + </li> + <li style="margin-bottom: 20px;"> + <span> Nov 16 2024 </span> + <p><h4><a href="/blog/optimizing-data-structures">Optimizing Data Structure for Performance</a></h4></p> + </li> + <li style="margin-bottom: 20px;"> + <span> Nov 13 2024 </span> + <p><h4><a href="/blog/wasm-bunny">WASM using c3</a></h4></p> + </li> + </ul> + </main> + <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> + <small>© 2026 June Park</small> +</div> + + </body> +</html>
--- a/mrjunejune/test/snapshots/index.html.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/index.html.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,7 +0,0 @@ -HTTP/1.1 301 Moved Permanently -Content-Type: text/plain -Content-Length: 0 -Connection: close -Body: -Location: / -
--- a/mrjunejune/test/snapshots/resume.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/resume.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,8 +1,3 @@ -HTTP/1.1 200 OK -Content-Type: text/html -Content-Length: 14484 -Connection: close - <!doctype html> <html lang="en"> <head> @@ -13,10 +8,10 @@ <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> +<!-- <link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> --> +<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> --> -<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> +<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> <link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
--- a/mrjunejune/test/snapshots/resume_index.html.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/resume_index.html.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,7 +0,0 @@ -HTTP/1.1 301 Moved Permanently -Content-Type: text/plain -Content-Length: 0 -Connection: close -Body: -Location: /resume -
--- a/mrjunejune/test/snapshots/root.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/root.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,8 +1,3 @@ -HTTP/1.1 200 OK -Content-Type: text/html -Content-Length: 5723 -Connection: close - <!doctype html> <html lang="en"> <head> @@ -14,10 +9,10 @@ <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> +<!-- <link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> --> +<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> --> -<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> +<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> <link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/test/snapshots/talk.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,223 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Talk!</title> + <meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> + +<link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> +<link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> + +<!-- <link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> --> +<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> --> + +<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> +<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> +<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> + +<link rel="preload" href="/base.css" as="style" /> +<link rel="stylesheet" href="/base.css" /> + + + <style> + body { font-family: sans-serif; padding: 20px; } + #messages { height: 200px; border: 1px solid #ccc; overflow-y: scroll; margin-bottom: 10px; padding: 10px; } + #chat { display: flex; gap: 10px; } + input { flex-grow: 1; } + </style> +</head> +<body> + <style> + :root { + --header-background: var(--white); + --header-color: rgb(var(--black)); + --link-hover-accent: var(--awesome); + } + + /* Fixed icon in top left corner */ + #themeToggle { + position: fixed; + top: 20px; + left: 20px; + background: var(--header-background); + display: flex; + align-items: center; + border-radius: 50%; + cursor: pointer; + z-index: 1000; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease; + } + + #themeToggle:hover { + transform: scale(1.05); + } + + /* Professional header */ + header { + margin: auto; + padding: 1.5em 1em; + font-family: "More", sans-serif; + box-shadow: 0 2px 8px rgba(var(--black), 5%); + width: 720px; + max-width: calc(100% - 2em); + text-align: center; + } + + header h1 { + margin: 0; + font-size: 1.8em; + font-weight: 700; + letter-spacing: -0.5px; + } + + header h1 a { + text-decoration: none; + color: var(--header-color); + } + + header h1 a::before { + display: none; + } + + /* Mobile responsiveness */ + @media (max-width: 720px) { + #themeToggle { + top: 15px; + left: 15px; + } + + header { + padding: 1em; + } + + header h1 { + font-size: 1.5em; + } + } + + @media (max-width: 480px) { + #themeToggle { + top: 10px; + left: 10px; + } + + #themeToggle img { + height: 40px; + width: 40px; + } + + header h1 { + font-size: 1.3em; + } + } + + #logo { + width: 300px; + } + + /* 1. DEFINE THE DEFAULTS (Light Mode) */ + :root { + --logo-invert: invert(0); + --epi-grayscale: grayscale(0) brightness(1); + } + + /* 2. MANUAL DARK OVERRIDE */ + html.dark { + --logo-invert: invert(1); + --epi-grayscale: grayscale(1); + } + + /* 3. MANUAL LIGHT OVERRIDE */ + html.light-mode { + --logo-invert: invert(0); + --epi-grayscale: brightness(2.9) grayscale(1); + } + + /* 4. SYSTEM PREFERENCE */ + @media (prefers-color-scheme: dark) { + :root:not(.light-mode) { + --logo-invert: invert(1); + } + } + + /* 5. APPLY TO ELEMENTS */ + #logo { + -webkit-filter: var(--logo-invert); + filter: var(--logo-invert); + transition: filter 0.3s ease; + } + + .epi-logo { + -webkit-filter: var(--epi-grayscale); + filter: var(--epi-grayscale); + transition: filter 0.3s ease; + } +</style> + +<div id="themeToggle"> + <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> +</div> + +<header> + <h1><a href="/">MrJuneJune</a></h1> +</header> +<script src="/index.js"></script> + + + <h1>Talks</h1> + + <div id="messages"></div> + + <div id="chat"> + <input type="text" id="messageInput" placeholder="Type a message..."> + <button id="sendBtn">Send</button> + </div> + <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> + <small>© 2026 June Park</small> +</div> + + <script> + const ws = new WebSocket('ws://localhost:6969/echo'); + const messagesDiv = document.getElementById('messages'); + + ws.onopen = () => { + console.log('Connected!'); + appendMessage('System: Connected to server'); + }; + + ws.onmessage = (event) => { + console.log('Received:', event.data); + appendMessage('Server: ' + event.data); + }; + + // Function to send message + sendBtn.onclick = () => { + const message = messageInput.value; + if (message) { + ws.send(message); + appendMessage('You: ' + message); + messageInput.value = ''; // Clear input + } + }; + + // Helper to show messages on screen + function appendMessage(text) { + const msg = document.createElement('p'); + msg.textContent = text; + messagesDiv.appendChild(msg); + messagesDiv.scrollTop = messagesDiv.scrollHeight; + } + + messageInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter' && !event.shiftKey) + { + event.preventDefault(); + sendBtn.click(); + } + }); + </script> +</body> +</html>
--- a/mrjunejune/test/snapshots/tools.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/tools.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,8 +1,3 @@ -HTTP/1.1 200 OK -Content-Type: text/html -Content-Length: 4246 -Connection: close - <!doctype html> <html lang="en"> <head> @@ -13,10 +8,10 @@ <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> +<!-- <link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> --> +<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> --> -<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> +<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> <link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
--- a/mrjunejune/test/snapshots/tools_file_converter.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/tools_file_converter.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,8 +1,3 @@ -HTTP/1.1 200 OK -Content-Type: text/html -Content-Length: 8526 -Connection: close - <!DOCTYPE html> <html lang="en"> <head> @@ -16,10 +11,10 @@ <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> +<!-- <link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> --> +<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> --> -<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> +<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> <link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
--- a/mrjunejune/test/snapshots/tools_file_converter_index.html.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/tools_file_converter_index.html.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,447 +0,0 @@ -HTTP/1.1 301 Moved Permanently -Content-Type: text/plain -Content-Length: 0 -Connection: close -Body: - -"font/woff" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> - -<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> - -<link rel="preload" href="/base.css" as="style" /> -<link rel="stylesheet" href="/base.css" /> - - - <link rel="stylesheet" href="markdown_to_html/index.css" /> -</head> -<body> - <style> - :root { - --header-background: var(--white); - --header-color: rgb(var(--black)); - --link-hover-accent: var(--awesome); - } - - /* Fixed icon in top left corner */ - #themeToggle { - position: fixed; - top: 20px; - left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; - z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - transition: transform 0.2s ease; - } - - #themeToggle:hover { - transform: scale(1.05); - } - - /* Professional header */ - header { - margin: auto; - padding: 1.5em 1em; - font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); - width: 720px; - max-width: calc(100% - 2em); - text-align: center; - } - - header h1 { - margin: 0; - font-size: 1.8em; - font-weight: 700; - letter-spacing: -0.5px; - } - - header h1 a { - text-decoration: none; - color: var(--header-color); - } - - header h1 a::before { - display: none; - } - - /* Mobile responsiveness */ - @media (max-width: 720px) { - #themeToggle { - top: 15px; - left: 15px; - } - - header { - padding: 1em; - } - - header h1 { - font-size: 1.5em; - } - } - - @media (max-width: 480px) { - #themeToggle { - top: 10px; - left: 10px; - } - - #themeToggle img { - height: 40px; - width: 40px; - } - - header h1 { - font-size: 1.3em; - } - } - - #logo { - width: 300px; - } - - /* 1. DEFINE THE DEFAULTS (Light Mode) */ - :root { - --logo-invert: invert(0); - --epi-grayscale: grayscale(0) brightness(1); - } - - /* 2. MANUAL DARK OVERRIDE */ - html.dark { - --logo-invert: invert(1); - --epi-grayscale: grayscale(1); - } - - /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { - --logo-invert: invert(0); - --epi-grayscale: brightness(2.9) grayscale(1); - } - - /* 4. SYSTEM PREFERENCE */ - @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { - --logo-invert: invert(1); - } - } - - /* 5. APPLY TO ELEMENTS */ - #logo { - -webkit-filter: var(--logo-invert); - filter: var(--logo-invert); - transition: filter 0.3s ease; - } - - .epi-logo { - -webkit-filter: var(--epi-grayscale); - filter: var(--epi-grayscale); - transition: filter 0.3s ease; - } -</style> - -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> - -<header> - <h1><a href="/">MrJuneJune</a></h1> -</header> -<script src="/index.js"></script> - - - <div class="header"> - <h1>Markdown to HTML Converter</h1> - </div> - - <div class="container"> - <div class="panel"> - <div class="label">Markdown Input</div> - <textarea id="input" placeholder="Type your markdown here..."># Welcome to Markdown Converter - -## Features - -This converter supports: - -- **Bold text** and *italic text* -- [Links](https://example.com) -- `inline code` -- ~~strikethrough~~ - -### Lists - -1. Ordered lists -2. Like this one -3. With numbers - -- Unordered lists -- Use dashes -- Or asterisks - -### Code Blocks - -``` -function example() { - return "Hello World"; -} -``` - -### Blockquotes - -> This is a blockquote -> It can span multiple lines - ---- - -### Images - - - -**Try editing this text!**</textarea> - </div> - - <div class="panel"> - <div class="title"> - <div class="label">HTML Output</div> - <button id="copy"> Copy </button> - </div> - <div id="output"></div> - </div> - </div> - <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> - <small>© 2026 June Park</small> -</div> - - <script src="/markdown_to_html.js"></script> - <script> - function convert() { - output.innerHTML = ''; - const markdown = input.value; - renderMarkdown(output, markdown); - } - input.addEventListener('input', convert); - - convert(); - - copy.addEventListener('click', () => { - const htmlBlob = new Blob([output.innerHTML], { type: 'text/html'}); - const textBlob = new Blob([output.innerText], { type: 'text/plain'}); - const data = [new ClipboardItem({ - 'text/html': htmlBlob, - 'text/plain': textBlob - })]; - navigator.clipboard.write(data).then(() => { - copy.textContent = "Copied!"; - setTimeout(() => { - copy.textContent = "Copy"; - copy.classList.remove('success'); - }, 1000); - }).catch(err => { - console.error('Failed to copy: ', err); - }); - }); - </script> -</body> -</htmlLocation: /tools - - - <span class="entry-title-style">Tools & Platforms:</span> - <span class="skill-type-style">Bazel, PostgresSQL, Mercurial, Git, Pands, Raylib, XCode</span> - </p> - <p> - <span class="entry-title-style"> Web Frameworks: </span> - <span class="skill-type-style"> Django, Rails, React, Flask</span> - </p> - <p> - <span class="entry-title-style"> DevOp:</span> - <span class="skill-type-style"> Plummi, Heroku, DigitalOcean, AWS, Google Cloud </span> - </p> - <p> - <span class="entry-title-style"> Language:</span> - <span class="skill-type-style"> English, Korean, Japanese </span> - </p> - </div> - - <!-- Experiences --> - <div class="sub-header"> - <h2 class="section-style"> Experience </h2> - <div class="line"></div> - </div> - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.meta.com/">Meta</a> - </p> - <p class="entry-location-style">San Francisco, CA, USA</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">Oct, 2024 - Present</p> - </div> - <ul class="description-style"> - <li> - Took initiative on Channel Value Rule, targeting the 16% of ad traffic with both app and web destinations to improve value attribution and ROI. - </li> - <li> - Built full-stack features using React and Hack/GraphQL, contributing to scalable, production-ready systems. - </li> - <li> - Partnered with data science to design A/B tests and analyze revenue impact of ads destination. - </li> - <li> - Proposed and implemented alpha improvements to internal testing infrastructure, reducing test time by 50% and enhancing developer velocity. - </li> - </ul> - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.wmg.com/">Warner Music Group</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">TECHNICAL LEAD ENGINEER</p> - <p class="entry-date-style">July, 2023 - Sept, 2024</p> - </div> - <ul class="description-style"> - <li> - Implements <a href="https://bazel.build/">bazel </a>structure for the company for TypeScript and JavaScript code base for hermiticity and stablishing standards for JavaScript and - </li> - <li> - TypeScript testing and code structures. - </li> - <li> - Led a team of five engineers in building GraphQL endpoints for client-facing applications using Apollo and AppSync, supporting over 2000 RPS and auto scaling depending on request values. - </li> - <li> - Improved application response times by up to 85% for graphQL response by updating database schema and SQL queries, eliminating N+1 queries and lack of indexes. - </li> - <li> - Developed CI/CD pipelines for backend structures. - </li> - <li> - Designed infrastructure for pub/sub, caching, and media processing logic. - </li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.google.com/">Google</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">Feb, 2022 - July 2023</p> - </div> - <ul class="description-style"> - <li> - Implements and maintained new features relating to App Script across google workspace platform including Gmail, sheets, and Docs.</li> - <li> - Improved a response time and render time of App Script hover card components.</li> - <li> - Collaborated with a team of developers to ensure timely and accurate delivery of features.</li> - <li> - Conducted user testing and gathered feedback to iterate on features for optimal user experience.</li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.everlywell.com/">Everlywell</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">December, 2020 - Jan, 2022</p> - </div> - <ul class="description-style"> - <li> - Maintained Amazon amplify apps to create and deploy React web applications for companies such as <a href="https://brooklynnets.everlywell.com/">NBA</a>, <a href="https://tinder.everlywell.com/">Tinder</a>, and other companies for COVID-19 at-home test kits.</li> - <li> - Implemented a script that helps accurately access and refund unused covid test kits; helping company save up to 200,000 USD.</li> - <li> - Created several Rails controllers for internal purposes; mocking end to end user experience for QA, mass refund features for CX department, and more, ultimately reducing support tickets amount by 50 percent.</li> - <li> - Implemented an audit table to help debug problems and logged which process was responsible for the change of the record using PaperTrail gems</li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.spiria.com/">Spiria</a> - </p> - <p class="entry-location-style">Oakville, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">October, 2018 - October, 2020</p> - </div> - <ul class="description-style"> - <li> - Constructed RESTful API endpoints in multiple different frameworks such as Django, Ruby on Rails, and Flask and automated API documentation process using swagger. - </li> - <li> - Designed custom rake tasks for importing production data into newly updated data structure to meet client's needs. - </li> - <li> - Maintained or updated staging/productions servers. Debugged problems in production postgres database using ssh and postgres console on Heroku or AWS servers - </li> - <li> - Collaborated in creating automation python scripts for websites and application using selenium covering for QA eliminating 80% of QA's manual work - </li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.apexscore.ai/">Apex Score</a> - </p> - <p class="entry-location-style">Oakville, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">September, 2019 - October, 2020</p> - </div> - <ul class="description-style"> - <li> - Developed custom Shapley value regression model to calculate importance of independent variables of data sets using sklearn, pandas, and numpy. - </li> - <li> - Created custom image uploader to Amazon s3 bucket using boto3 library. - </li> - <li> - Built RESTful API application using Flask framework and automated extensive API documentation pages using flask-restplus, pytest, and swagger, covering 95% of the code base. - </li> - <li> - Created an interactive graph using D3.js in Vue.js with data from Flask backend API. - </li> - </ul> - - <div class="sub-header"> - <h2 class="section-style"> Education </h2> - <div class="line"></div> - </div> - <div class="flex-box"> - <p class="entry-title-style"> - University of British Columbia - </p> - <p class="entry-location-style">Kelowna, British Columbia</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">BACHELOR OF SCIENCE IN PHYSICS</p> - <p class="entry-date-style">2014 - 2018</p> - </div> - <div id="footer"></div> - </main> - <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> - <small>© 2026 June Park</small> -</div> - - <script href="index.js"></script> - </body> -</htmlLocation: /tools/markdown_to_html -Location: /tools/file_converter -
--- a/mrjunejune/test/snapshots/tools_index.html.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/tools_index.html.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,445 +0,0 @@ -HTTP/1.1 301 Moved Permanently -Content-Type: text/plain -Content-Length: 0 -Connection: close -Body: - -"font/woff" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> - -<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> - -<link rel="preload" href="/base.css" as="style" /> -<link rel="stylesheet" href="/base.css" /> - - - <link rel="stylesheet" href="markdown_to_html/index.css" /> -</head> -<body> - <style> - :root { - --header-background: var(--white); - --header-color: rgb(var(--black)); - --link-hover-accent: var(--awesome); - } - - /* Fixed icon in top left corner */ - #themeToggle { - position: fixed; - top: 20px; - left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; - z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - transition: transform 0.2s ease; - } - - #themeToggle:hover { - transform: scale(1.05); - } - - /* Professional header */ - header { - margin: auto; - padding: 1.5em 1em; - font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); - width: 720px; - max-width: calc(100% - 2em); - text-align: center; - } - - header h1 { - margin: 0; - font-size: 1.8em; - font-weight: 700; - letter-spacing: -0.5px; - } - - header h1 a { - text-decoration: none; - color: var(--header-color); - } - - header h1 a::before { - display: none; - } - - /* Mobile responsiveness */ - @media (max-width: 720px) { - #themeToggle { - top: 15px; - left: 15px; - } - - header { - padding: 1em; - } - - header h1 { - font-size: 1.5em; - } - } - - @media (max-width: 480px) { - #themeToggle { - top: 10px; - left: 10px; - } - - #themeToggle img { - height: 40px; - width: 40px; - } - - header h1 { - font-size: 1.3em; - } - } - - #logo { - width: 300px; - } - - /* 1. DEFINE THE DEFAULTS (Light Mode) */ - :root { - --logo-invert: invert(0); - --epi-grayscale: grayscale(0) brightness(1); - } - - /* 2. MANUAL DARK OVERRIDE */ - html.dark { - --logo-invert: invert(1); - --epi-grayscale: grayscale(1); - } - - /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { - --logo-invert: invert(0); - --epi-grayscale: brightness(2.9) grayscale(1); - } - - /* 4. SYSTEM PREFERENCE */ - @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { - --logo-invert: invert(1); - } - } - - /* 5. APPLY TO ELEMENTS */ - #logo { - -webkit-filter: var(--logo-invert); - filter: var(--logo-invert); - transition: filter 0.3s ease; - } - - .epi-logo { - -webkit-filter: var(--epi-grayscale); - filter: var(--epi-grayscale); - transition: filter 0.3s ease; - } -</style> - -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> - -<header> - <h1><a href="/">MrJuneJune</a></h1> -</header> -<script src="/index.js"></script> - - - <div class="header"> - <h1>Markdown to HTML Converter</h1> - </div> - - <div class="container"> - <div class="panel"> - <div class="label">Markdown Input</div> - <textarea id="input" placeholder="Type your markdown here..."># Welcome to Markdown Converter - -## Features - -This converter supports: - -- **Bold text** and *italic text* -- [Links](https://example.com) -- `inline code` -- ~~strikethrough~~ - -### Lists - -1. Ordered lists -2. Like this one -3. With numbers - -- Unordered lists -- Use dashes -- Or asterisks - -### Code Blocks - -``` -function example() { - return "Hello World"; -} -``` - -### Blockquotes - -> This is a blockquote -> It can span multiple lines - ---- - -### Images - - - -**Try editing this text!**</textarea> - </div> - - <div class="panel"> - <div class="title"> - <div class="label">HTML Output</div> - <button id="copy"> Copy </button> - </div> - <div id="output"></div> - </div> - </div> - <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> - <small>© 2026 June Park</small> -</div> - - <script src="/markdown_to_html.js"></script> - <script> - function convert() { - output.innerHTML = ''; - const markdown = input.value; - renderMarkdown(output, markdown); - } - input.addEventListener('input', convert); - - convert(); - - copy.addEventListener('click', () => { - const htmlBlob = new Blob([output.innerHTML], { type: 'text/html'}); - const textBlob = new Blob([output.innerText], { type: 'text/plain'}); - const data = [new ClipboardItem({ - 'text/html': htmlBlob, - 'text/plain': textBlob - })]; - navigator.clipboard.write(data).then(() => { - copy.textContent = "Copied!"; - setTimeout(() => { - copy.textContent = "Copy"; - copy.classList.remove('success'); - }, 1000); - }).catch(err => { - console.error('Failed to copy: ', err); - }); - }); - </script> -</body> -</htmlLocation: /tools - - - <span class="entry-title-style">Tools & Platforms:</span> - <span class="skill-type-style">Bazel, PostgresSQL, Mercurial, Git, Pands, Raylib, XCode</span> - </p> - <p> - <span class="entry-title-style"> Web Frameworks: </span> - <span class="skill-type-style"> Django, Rails, React, Flask</span> - </p> - <p> - <span class="entry-title-style"> DevOp:</span> - <span class="skill-type-style"> Plummi, Heroku, DigitalOcean, AWS, Google Cloud </span> - </p> - <p> - <span class="entry-title-style"> Language:</span> - <span class="skill-type-style"> English, Korean, Japanese </span> - </p> - </div> - - <!-- Experiences --> - <div class="sub-header"> - <h2 class="section-style"> Experience </h2> - <div class="line"></div> - </div> - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.meta.com/">Meta</a> - </p> - <p class="entry-location-style">San Francisco, CA, USA</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">Oct, 2024 - Present</p> - </div> - <ul class="description-style"> - <li> - Took initiative on Channel Value Rule, targeting the 16% of ad traffic with both app and web destinations to improve value attribution and ROI. - </li> - <li> - Built full-stack features using React and Hack/GraphQL, contributing to scalable, production-ready systems. - </li> - <li> - Partnered with data science to design A/B tests and analyze revenue impact of ads destination. - </li> - <li> - Proposed and implemented alpha improvements to internal testing infrastructure, reducing test time by 50% and enhancing developer velocity. - </li> - </ul> - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.wmg.com/">Warner Music Group</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">TECHNICAL LEAD ENGINEER</p> - <p class="entry-date-style">July, 2023 - Sept, 2024</p> - </div> - <ul class="description-style"> - <li> - Implements <a href="https://bazel.build/">bazel </a>structure for the company for TypeScript and JavaScript code base for hermiticity and stablishing standards for JavaScript and - </li> - <li> - TypeScript testing and code structures. - </li> - <li> - Led a team of five engineers in building GraphQL endpoints for client-facing applications using Apollo and AppSync, supporting over 2000 RPS and auto scaling depending on request values. - </li> - <li> - Improved application response times by up to 85% for graphQL response by updating database schema and SQL queries, eliminating N+1 queries and lack of indexes. - </li> - <li> - Developed CI/CD pipelines for backend structures. - </li> - <li> - Designed infrastructure for pub/sub, caching, and media processing logic. - </li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.google.com/">Google</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">Feb, 2022 - July 2023</p> - </div> - <ul class="description-style"> - <li> - Implements and maintained new features relating to App Script across google workspace platform including Gmail, sheets, and Docs.</li> - <li> - Improved a response time and render time of App Script hover card components.</li> - <li> - Collaborated with a team of developers to ensure timely and accurate delivery of features.</li> - <li> - Conducted user testing and gathered feedback to iterate on features for optimal user experience.</li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.everlywell.com/">Everlywell</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">December, 2020 - Jan, 2022</p> - </div> - <ul class="description-style"> - <li> - Maintained Amazon amplify apps to create and deploy React web applications for companies such as <a href="https://brooklynnets.everlywell.com/">NBA</a>, <a href="https://tinder.everlywell.com/">Tinder</a>, and other companies for COVID-19 at-home test kits.</li> - <li> - Implemented a script that helps accurately access and refund unused covid test kits; helping company save up to 200,000 USD.</li> - <li> - Created several Rails controllers for internal purposes; mocking end to end user experience for QA, mass refund features for CX department, and more, ultimately reducing support tickets amount by 50 percent.</li> - <li> - Implemented an audit table to help debug problems and logged which process was responsible for the change of the record using PaperTrail gems</li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.spiria.com/">Spiria</a> - </p> - <p class="entry-location-style">Oakville, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">October, 2018 - October, 2020</p> - </div> - <ul class="description-style"> - <li> - Constructed RESTful API endpoints in multiple different frameworks such as Django, Ruby on Rails, and Flask and automated API documentation process using swagger. - </li> - <li> - Designed custom rake tasks for importing production data into newly updated data structure to meet client's needs. - </li> - <li> - Maintained or updated staging/productions servers. Debugged problems in production postgres database using ssh and postgres console on Heroku or AWS servers - </li> - <li> - Collaborated in creating automation python scripts for websites and application using selenium covering for QA eliminating 80% of QA's manual work - </li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.apexscore.ai/">Apex Score</a> - </p> - <p class="entry-location-style">Oakville, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">September, 2019 - October, 2020</p> - </div> - <ul class="description-style"> - <li> - Developed custom Shapley value regression model to calculate importance of independent variables of data sets using sklearn, pandas, and numpy. - </li> - <li> - Created custom image uploader to Amazon s3 bucket using boto3 library. - </li> - <li> - Built RESTful API application using Flask framework and automated extensive API documentation pages using flask-restplus, pytest, and swagger, covering 95% of the code base. - </li> - <li> - Created an interactive graph using D3.js in Vue.js with data from Flask backend API. - </li> - </ul> - - <div class="sub-header"> - <h2 class="section-style"> Education </h2> - <div class="line"></div> - </div> - <div class="flex-box"> - <p class="entry-title-style"> - University of British Columbia - </p> - <p class="entry-location-style">Kelowna, British Columbia</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">BACHELOR OF SCIENCE IN PHYSICS</p> - <p class="entry-date-style">2014 - 2018</p> - </div> - <div id="footer"></div> - </main> - <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> - <small>© 2026 June Park</small> -</div> - - <script href="index.js"></script> - </body> -</html>
--- a/mrjunejune/test/snapshots/tools_markdown_to_html.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/tools_markdown_to_html.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,8 +1,3 @@ -HTTP/1.1 200 OK -Content-Type: text/html -Content-Length: 5985 -Connection: close - <!DOCTYPE html> <html lang="en"> <head> @@ -16,10 +11,10 @@ <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> +<!-- <link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin> --> +<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> --> -<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> +<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> <link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
--- a/mrjunejune/test/snapshots/tools_markdown_to_html_index.html.snapshot Fri Jan 09 07:19:09 2026 -0800 +++ b/mrjunejune/test/snapshots/tools_markdown_to_html_index.html.snapshot Fri Jan 09 07:42:04 2026 -0800 @@ -1,446 +0,0 @@ -HTTP/1.1 301 Moved Permanently -Content-Type: text/plain -Content-Length: 0 -Connection: close -Body: - -"font/woff" crossorigin> -<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> - -<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> - -<link rel="preload" href="/base.css" as="style" /> -<link rel="stylesheet" href="/base.css" /> - - - <link rel="stylesheet" href="markdown_to_html/index.css" /> -</head> -<body> - <style> - :root { - --header-background: var(--white); - --header-color: rgb(var(--black)); - --link-hover-accent: var(--awesome); - } - - /* Fixed icon in top left corner */ - #themeToggle { - position: fixed; - top: 20px; - left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; - z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - transition: transform 0.2s ease; - } - - #themeToggle:hover { - transform: scale(1.05); - } - - /* Professional header */ - header { - margin: auto; - padding: 1.5em 1em; - font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); - width: 720px; - max-width: calc(100% - 2em); - text-align: center; - } - - header h1 { - margin: 0; - font-size: 1.8em; - font-weight: 700; - letter-spacing: -0.5px; - } - - header h1 a { - text-decoration: none; - color: var(--header-color); - } - - header h1 a::before { - display: none; - } - - /* Mobile responsiveness */ - @media (max-width: 720px) { - #themeToggle { - top: 15px; - left: 15px; - } - - header { - padding: 1em; - } - - header h1 { - font-size: 1.5em; - } - } - - @media (max-width: 480px) { - #themeToggle { - top: 10px; - left: 10px; - } - - #themeToggle img { - height: 40px; - width: 40px; - } - - header h1 { - font-size: 1.3em; - } - } - - #logo { - width: 300px; - } - - /* 1. DEFINE THE DEFAULTS (Light Mode) */ - :root { - --logo-invert: invert(0); - --epi-grayscale: grayscale(0) brightness(1); - } - - /* 2. MANUAL DARK OVERRIDE */ - html.dark { - --logo-invert: invert(1); - --epi-grayscale: grayscale(1); - } - - /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { - --logo-invert: invert(0); - --epi-grayscale: brightness(2.9) grayscale(1); - } - - /* 4. SYSTEM PREFERENCE */ - @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { - --logo-invert: invert(1); - } - } - - /* 5. APPLY TO ELEMENTS */ - #logo { - -webkit-filter: var(--logo-invert); - filter: var(--logo-invert); - transition: filter 0.3s ease; - } - - .epi-logo { - -webkit-filter: var(--epi-grayscale); - filter: var(--epi-grayscale); - transition: filter 0.3s ease; - } -</style> - -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> - -<header> - <h1><a href="/">MrJuneJune</a></h1> -</header> -<script src="/index.js"></script> - - - <div class="header"> - <h1>Markdown to HTML Converter</h1> - </div> - - <div class="container"> - <div class="panel"> - <div class="label">Markdown Input</div> - <textarea id="input" placeholder="Type your markdown here..."># Welcome to Markdown Converter - -## Features - -This converter supports: - -- **Bold text** and *italic text* -- [Links](https://example.com) -- `inline code` -- ~~strikethrough~~ - -### Lists - -1. Ordered lists -2. Like this one -3. With numbers - -- Unordered lists -- Use dashes -- Or asterisks - -### Code Blocks - -``` -function example() { - return "Hello World"; -} -``` - -### Blockquotes - -> This is a blockquote -> It can span multiple lines - ---- - -### Images - - - -**Try editing this text!**</textarea> - </div> - - <div class="panel"> - <div class="title"> - <div class="label">HTML Output</div> - <button id="copy"> Copy </button> - </div> - <div id="output"></div> - </div> - </div> - <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> - <small>© 2026 June Park</small> -</div> - - <script src="/markdown_to_html.js"></script> - <script> - function convert() { - output.innerHTML = ''; - const markdown = input.value; - renderMarkdown(output, markdown); - } - input.addEventListener('input', convert); - - convert(); - - copy.addEventListener('click', () => { - const htmlBlob = new Blob([output.innerHTML], { type: 'text/html'}); - const textBlob = new Blob([output.innerText], { type: 'text/plain'}); - const data = [new ClipboardItem({ - 'text/html': htmlBlob, - 'text/plain': textBlob - })]; - navigator.clipboard.write(data).then(() => { - copy.textContent = "Copied!"; - setTimeout(() => { - copy.textContent = "Copy"; - copy.classList.remove('success'); - }, 1000); - }).catch(err => { - console.error('Failed to copy: ', err); - }); - }); - </script> -</body> -</htmlLocation: /tools - - - <span class="entry-title-style">Tools & Platforms:</span> - <span class="skill-type-style">Bazel, PostgresSQL, Mercurial, Git, Pands, Raylib, XCode</span> - </p> - <p> - <span class="entry-title-style"> Web Frameworks: </span> - <span class="skill-type-style"> Django, Rails, React, Flask</span> - </p> - <p> - <span class="entry-title-style"> DevOp:</span> - <span class="skill-type-style"> Plummi, Heroku, DigitalOcean, AWS, Google Cloud </span> - </p> - <p> - <span class="entry-title-style"> Language:</span> - <span class="skill-type-style"> English, Korean, Japanese </span> - </p> - </div> - - <!-- Experiences --> - <div class="sub-header"> - <h2 class="section-style"> Experience </h2> - <div class="line"></div> - </div> - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.meta.com/">Meta</a> - </p> - <p class="entry-location-style">San Francisco, CA, USA</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">Oct, 2024 - Present</p> - </div> - <ul class="description-style"> - <li> - Took initiative on Channel Value Rule, targeting the 16% of ad traffic with both app and web destinations to improve value attribution and ROI. - </li> - <li> - Built full-stack features using React and Hack/GraphQL, contributing to scalable, production-ready systems. - </li> - <li> - Partnered with data science to design A/B tests and analyze revenue impact of ads destination. - </li> - <li> - Proposed and implemented alpha improvements to internal testing infrastructure, reducing test time by 50% and enhancing developer velocity. - </li> - </ul> - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.wmg.com/">Warner Music Group</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">TECHNICAL LEAD ENGINEER</p> - <p class="entry-date-style">July, 2023 - Sept, 2024</p> - </div> - <ul class="description-style"> - <li> - Implements <a href="https://bazel.build/">bazel </a>structure for the company for TypeScript and JavaScript code base for hermiticity and stablishing standards for JavaScript and - </li> - <li> - TypeScript testing and code structures. - </li> - <li> - Led a team of five engineers in building GraphQL endpoints for client-facing applications using Apollo and AppSync, supporting over 2000 RPS and auto scaling depending on request values. - </li> - <li> - Improved application response times by up to 85% for graphQL response by updating database schema and SQL queries, eliminating N+1 queries and lack of indexes. - </li> - <li> - Developed CI/CD pipelines for backend structures. - </li> - <li> - Designed infrastructure for pub/sub, caching, and media processing logic. - </li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.google.com/">Google</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">Feb, 2022 - July 2023</p> - </div> - <ul class="description-style"> - <li> - Implements and maintained new features relating to App Script across google workspace platform including Gmail, sheets, and Docs.</li> - <li> - Improved a response time and render time of App Script hover card components.</li> - <li> - Collaborated with a team of developers to ensure timely and accurate delivery of features.</li> - <li> - Conducted user testing and gathered feedback to iterate on features for optimal user experience.</li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.everlywell.com/">Everlywell</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">December, 2020 - Jan, 2022</p> - </div> - <ul class="description-style"> - <li> - Maintained Amazon amplify apps to create and deploy React web applications for companies such as <a href="https://brooklynnets.everlywell.com/">NBA</a>, <a href="https://tinder.everlywell.com/">Tinder</a>, and other companies for COVID-19 at-home test kits.</li> - <li> - Implemented a script that helps accurately access and refund unused covid test kits; helping company save up to 200,000 USD.</li> - <li> - Created several Rails controllers for internal purposes; mocking end to end user experience for QA, mass refund features for CX department, and more, ultimately reducing support tickets amount by 50 percent.</li> - <li> - Implemented an audit table to help debug problems and logged which process was responsible for the change of the record using PaperTrail gems</li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.spiria.com/">Spiria</a> - </p> - <p class="entry-location-style">Oakville, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">October, 2018 - October, 2020</p> - </div> - <ul class="description-style"> - <li> - Constructed RESTful API endpoints in multiple different frameworks such as Django, Ruby on Rails, and Flask and automated API documentation process using swagger. - </li> - <li> - Designed custom rake tasks for importing production data into newly updated data structure to meet client's needs. - </li> - <li> - Maintained or updated staging/productions servers. Debugged problems in production postgres database using ssh and postgres console on Heroku or AWS servers - </li> - <li> - Collaborated in creating automation python scripts for websites and application using selenium covering for QA eliminating 80% of QA's manual work - </li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.apexscore.ai/">Apex Score</a> - </p> - <p class="entry-location-style">Oakville, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">September, 2019 - October, 2020</p> - </div> - <ul class="description-style"> - <li> - Developed custom Shapley value regression model to calculate importance of independent variables of data sets using sklearn, pandas, and numpy. - </li> - <li> - Created custom image uploader to Amazon s3 bucket using boto3 library. - </li> - <li> - Built RESTful API application using Flask framework and automated extensive API documentation pages using flask-restplus, pytest, and swagger, covering 95% of the code base. - </li> - <li> - Created an interactive graph using D3.js in Vue.js with data from Flask backend API. - </li> - </ul> - - <div class="sub-header"> - <h2 class="section-style"> Education </h2> - <div class="line"></div> - </div> - <div class="flex-box"> - <p class="entry-title-style"> - University of British Columbia - </p> - <p class="entry-location-style">Kelowna, British Columbia</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">BACHELOR OF SCIENCE IN PHYSICS</p> - <p class="entry-date-style">2014 - 2018</p> - </div> - <div id="footer"></div> - </main> - <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> - <small>© 2026 June Park</small> -</div> - - <script href="index.js"></script> - </body> -</htmlLocation: /tools/markdown_to_html -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/test/test.h Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,74 @@ +#include "seobeo/seobeo.h" +#include "seobeo/snapshot_creator.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> +#include <signal.h> + +#define TEST_PORT "6969" +#define TEST_HOST "127.0.0.1" +#define TEST_URL "http://127.0.0.1:6969" +#define MAX_RESPONSE_SIZE (1024 * 1024) +#ifndef SNAPSHOT_DIR + #define SNAPSHOT_DIR "mrjunejune/test/snapshots" +#endif + +pid_t start_test_server(const char *server_binary); +void stop_test_server(pid_t server_pid); + + +pid_t start_test_server(const char *server_binary) +{ + pid_t server_pid = fork(); + + if (server_pid < 0) + { + perror("fork"); + return -1; + } + + if (server_pid == 0) + { + printf("Starting server on port %s...\n", TEST_PORT); + execl(server_binary, server_binary, NULL); + perror("execl failed"); + exit(1); + } + + printf("Server started (PID: %d)\n", server_pid); + + usleep(100000); + int status; + pid_t result = waitpid(server_pid, &status, WNOHANG); + if (result != 0) + { + if (WIFEXITED(status)) + { + fprintf(stderr, "Server exited immediately with code: %d\n", WEXITSTATUS(status)); + } + else if (WIFSIGNALED(status)) + { + fprintf(stderr, "Server was killed by signal: %d\n", WTERMSIG(status)); + } + return -1; + } + + sleep(2); + printf("Server ready\n\n"); + + return server_pid; +} + +void stop_test_server(pid_t server_pid) +{ + if (server_pid > 0) + { + printf("\nStopping server (PID: %d)...\n", server_pid); + kill(server_pid, SIGTERM); + waitpid(server_pid, NULL, 0); + printf("Server stopped\n"); + } +} + +
--- a/playground/.gitignore Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -# dependencies (bun install) -node_modules - -# output -out -dist -*.tgz - -# code coverage -coverage -*.lcov - -# logs -logs -_.log -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# caches -.eslintcache -.cache -*.tsbuildinfo - -# IntelliJ based IDEs -.idea - -# Finder (MacOS) folder config -.DS_Store
--- a/playground/BUILD Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -load("@rules_cc//cc:cc_binary.bzl", "cc_binary") -load("//gui_ze:gui_ze.bzl", "bun_build") - -cc_binary( - name = "main", - srcs = ["main.c"], -) - -filegroup( - name = "all_ts_files", - srcs = glob([ - "**/*.ts", - "**/*.tsx", - "**/*.js", - "**/*.jsx", - ], allow_empty=True) -) - -bun_build( - name = "hello", - src = "main.ts", - src_folder = "playground", - data = [ - "//third_party/bun:bun_files", - ":all_ts_files", - ], - visibility = ["//visibility:public"], -)
--- a/playground/README.md Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -# playground - -To install dependencies: - -```bash -bun install -``` - -To run: - -```bash -bun run -``` - -This project was created using `bun init` in bun v1.2.23. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
--- a/playground/foo/foo.ts Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -export const foo = () => 10;
--- a/playground/hello.ts Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -const JUNE = "JUNE"; - -export { - JUNE, -}
--- a/playground/june.tsx Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -import react from "React" - -const Foo = () => { - return ( - <> hehexd </> - ); -} - -export { - Foo, -}
--- a/playground/main.c Fri Jan 09 07:19:09 2026 -0800 +++ b/playground/main.c Fri Jan 09 07:42:04 2026 -0800 @@ -1,42 +1,65 @@ +#include <stdlib.h> #include <stdio.h> -#include <stdlib.h> - -#define REPO_ROOT "/Users/mrjunejune/zenbu" +#include <string.h> -int main() -{ - char command[512]; - snprintf(command, sizeof(command), "hg -R %s serve --stdio", REPO_ROOT); - printf("command: %s\n", command); - - FILE *hg_pipe = popen(command, "r+"); - if (!hg_pipe) - { - printf("Failed to open pipe\n"); - return -1; - } +int main() { + char *input[5] = {"Hello ", "Foo <<E", "N", "DD>", "Park <END> "}; + char *key = "<END>"; + int key_len = strlen(key); - fprintf(hg_pipe, "capabilities\n"); - fflush(hg_pipe); + char **buffers = malloc(sizeof(char *) * 100); + int buffer_index = 0; + int key_ptr = 0; + for (int i = 0; i < 5; i++) { + char *packet = input[i]; + int p_len = strlen(packet); - char *output = malloc(sizeof(char) * 2048); - char *curr = output; - int c; - int number_of_breakline = 0; - while ((c = fgetc(hg_pipe)) != NULL) - { - *curr++ = c; - printf("output: %s\n", output); - if (c == '\n') - number_of_breakline++; - if (number_of_breakline == 2) - break; - printf("char: %c\n", c); - } - pclose(hg_pipe); + for (int j = 0; j < p_len; j++) { + if (packet[j] == key[key_ptr]) { + key_ptr++; + + // If the WHOLE keyword is found + if (key_ptr == key_len) { + // 1. Print all previous "safe" buffers + // (The ones before the one where the keyword started) + for (int b = 0; b < buffer_index; b++) + printf("%s", buffers[b]); + + // 2. Handle the "Current" packet truncation + // Calculate where the match started in THIS packet + // If key_ptr was satisfied across multiple packets, + // 'j' is the end of the match in the current packet. + int match_end_in_packet = j + 1; + int match_len_in_this_packet = (key_ptr <= match_end_in_packet) ? key_ptr : match_end_in_packet; + + printf("%.*s\n", (match_end_in_packet - match_len_in_this_packet), packet); + + free(buffers); + return 0; + } + } else { + // If a match fails, we must "flush" the buffers we were holding + if (key_ptr > 0) { + for (int b = 0; b < buffer_index; b++) printf("%s", buffers[b]); + buffer_index = 0; + + if (packet[j] == key[0]) key_ptr = 1; + else { + printf("%c", packet[j]); + key_ptr = 0; + } + } else { + printf("%c", packet[j]); + } + } + } + // If we finish a packet and we are in the middle of a match, buffer it + if (key_ptr > 0) { + buffers[buffer_index++] = packet; + } + } - return 0; + free(buffers); + return 0; } - -
--- a/playground/main.ts Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -import { Foo } from "./june"; -import ReactDOM from 'react-dom/client'; - -ReactDOM.createRoot(document.getElementById('root')!).render(Foo()); -
--- a/playground/new.txt Fri Jan 09 07:19:09 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -hello good sir
--- a/seobeo/BUILD Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/BUILD Fri Jan 09 07:42:04 2026 -0800 @@ -1,7 +1,6 @@ -load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_library.bzl", "cc_library") -load("@rules_cc//cc:cc_test.bzl", "cc_test") +# File group filegroup( name = "seobeo_hdrs", srcs = [ @@ -12,31 +11,72 @@ visibility = ["//visibility:public"], ) +# Minimal TCP/SSL handling only (no HTTP, no WebSocket) alias( - name = "seobeo_server", + name = "seobeo_min", actual = select({ - "//config:macos": ":seobeo_server_macos", - "//config:linux": ":seobeo_server_linux", - "//conditions:default": ":seobeo_server_linux", - }), - visibility = ["//visibility:public"], -) - -alias( - name = "seobeo_server_dev", - actual = select({ - "//config:macos": ":seobeo_server_macos_dev", - "//config:linux": ":seobeo_server_linux_dev", - "//conditions:default": ":seobeo_server_linux_dev", + "//config:macos": ":seobeo_min_macos", + "//config:linux": ":seobeo_min_linux", + "//conditions:default": ":seobeo_min_linux", }), visibility = ["//visibility:public"], ) cc_library( - name = "seobeo_server_macos", + name = "seobeo_min_macos", + srcs = [ + "s_network.c", + "s_logging.c", + "s_ssl.c", + "os/s_macos_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + target_compatible_with = [ + "@platforms//os:osx", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_min_linux", srcs = [ - "s__network.c", + "s_network.c", + "s_logging.c", + "s_ssl.c", + "os/s_linux_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + target_compatible_with = [ + "@platforms//os:linux", + ], + visibility = ["//visibility:public"], +) + +# TCP Server with HTTP (no WebSocket, no SSL) +alias( + name = "seobeo_tcp_server", + actual = select({ + "//config:macos": ":seobeo_tcp_server_macos", + "//config:linux": ":seobeo_tcp_server_linux", + "//conditions:default": ":seobeo_tcp_server_linux", + }), + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_tcp_server_macos", + srcs = [ + "s_network.c", "s_web.c", + "s_logging.c", "s_ssl.c", "os/s_macos_edge.c", ], @@ -52,29 +92,11 @@ ) cc_library( - name = "seobeo_server_macos_dev", + name = "seobeo_tcp_server_linux", srcs = [ "s_network.c", "s_web.c", - "s_ssl.c", - "os/s_macos_edge.c", - ], - hdrs = [":seobeo_hdrs"], - deps = [ - "//dowa:dowa", - ], - defines = ["SEOBEO_NO_SSL", "SEOBEO_ENABLE_DEBUG"], - target_compatible_with = [ - "@platforms//os:osx", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "seobeo_server_linux", - srcs = [ - "s_network.c", - "s_web.c", + "s_logging.c", "s_ssl.c", "os/s_linux_edge.c", ], @@ -89,45 +111,81 @@ visibility = ["//visibility:public"], ) +# TCP Server with HTTP + WebSocket +alias( + name = "seobeo_tcp_server_ws", + actual = select({ + "//config:macos": ":seobeo_tcp_server_ws_macos", + "//config:linux": ":seobeo_tcp_server_ws_linux", + "//conditions:default": ":seobeo_tcp_server_ws_linux", + }), + visibility = ["//visibility:public"], +) + cc_library( - name = "seobeo_server_linux_dev", + name = "seobeo_tcp_server_ws_macos", srcs = [ "s_network.c", "s_web.c", + "s_logging.c", "s_ssl.c", + "s_websocket_common.c", + "s_websocket_server.c", + "os/s_macos_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + defines = ["SEOBEO_WEBSOCKET_SERVER"], + target_compatible_with = [ + "@platforms//os:osx", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_tcp_server_ws_linux", + srcs = [ + "s_network.c", + "s_web.c", + "s_logging.c", + "s_ssl.c", + "s_websocket_common.c", + "s_websocket_server.c", "os/s_linux_edge.c", ], hdrs = [":seobeo_hdrs"], deps = [ "//dowa:dowa", + "@openssl//:ssl", ], - defines = ["SEOBEO_NO_SSL", "SEOBEO_ENABLE_DEBUG"], + defines = ["SEOBEO_WEBSOCKET_SERVER"], target_compatible_with = [ "@platforms//os:linux", ], visibility = ["//visibility:public"], ) -# Client target with SSL support (full functionality) +# TCP Client with HTTP alias( - name = "seobeo_client", + name = "seobeo_tcp_client", actual = select({ - "//config:macos": ":seobeo_client_macos", - "//config:linux": ":seobeo_client_linux", - "//conditions:default": ":seobeo_client_linux", + "//config:macos": ":seobeo_tcp_client_macos", + "//config:linux": ":seobeo_tcp_client_linux", + "//conditions:default": ":seobeo_tcp_client_linux", }), visibility = ["//visibility:public"], ) cc_library( - name = "seobeo_client_macos", + name = "seobeo_tcp_client_macos", srcs = [ "s_network.c", - "s_web.c", + "s_logging.c", "s_ssl.c", "s_http_client.c", - "s_websocket.c", - "s_websocket_server.c", "snapshot_creator.c", "os/s_macos_edge.c", ], @@ -143,14 +201,69 @@ ) cc_library( - name = "seobeo_client_linux", + name = "seobeo_tcp_client_linux", srcs = [ "s_network.c", - "s_web.c", + "s_logging.c", "s_ssl.c", "s_http_client.c", + "snapshot_creator.c", + "os/s_linux_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + target_compatible_with = [ + "@platforms//os:linux", + ], + visibility = ["//visibility:public"], +) + +# TCP Client with HTTP + WebSocket +alias( + name = "seobeo_tcp_client_ws", + actual = select({ + "//config:macos": ":seobeo_tcp_client_ws_macos", + "//config:linux": ":seobeo_tcp_client_ws_linux", + "//conditions:default": ":seobeo_tcp_client_ws_linux", + }), + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_tcp_client_ws_macos", + srcs = [ + "s_network.c", + "s_logging.c", + "s_ssl.c", + "s_http_client.c", + "s_websocket_common.c", "s_websocket.c", - "s_websocket_server.c", + "snapshot_creator.c", + "os/s_macos_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + target_compatible_with = [ + "@platforms//os:osx", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_tcp_client_ws_linux", + srcs = [ + "s_network.c", + "s_logging.c", + "s_ssl.c", + "s_http_client.c", + "s_websocket_common.c", + "s_websocket.c", "snapshot_creator.c", "os/s_linux_edge.c", ], @@ -165,46 +278,126 @@ visibility = ["//visibility:public"], ) -# Default target for backward compatibility (points to client with SSL) +# All combined only used this if you don't want to deal with imports and what not... alias( name = "seobeo", - actual = ":seobeo_client", + actual = select({ + "//config:macos": ":seobeo_macos", + "//config:linux": ":seobeo_linux", + "//conditions:default": ":seobeo_linux", + }), visibility = ["//visibility:public"], ) -# Examples -cc_binary( - name = "websocket_server_example", - srcs = ["examples/websocket_server_example.c"], - deps = [":seobeo_client"], +cc_library( + name = "seobeo_macos", + srcs = [ + "s_network.c", + "s_web.c", + "s_logging.c", + "s_ssl.c", + "s_http_client.c", + "s_websocket_common.c", + "s_websocket.c", + "s_websocket_server.c", + "snapshot_creator.c", + "os/s_macos_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + defines = ["SEOBEO_WEBSOCKET_SERVER"], + target_compatible_with = [ + "@platforms//os:osx", + ], visibility = ["//visibility:public"], ) -# Tests -cc_test( - name = "seobeo_client_test", - srcs = ["tests/seobeo_client_test.c"], - deps = [":seobeo_client"], +cc_library( + name = "seobeo_linux", + srcs = [ + "s_network.c", + "s_web.c", + "s_logging.c", + "s_ssl.c", + "s_http_client.c", + "s_websocket_common.c", + "s_websocket.c", + "s_websocket_server.c", + "snapshot_creator.c", + "os/s_linux_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + defines = ["SEOBEO_WEBSOCKET_SERVER"], + target_compatible_with = [ + "@platforms//os:linux", + ], + visibility = ["//visibility:public"], +) + +# Names I often use +alias( + name = "seobeo_server", + actual = ":seobeo_tcp_server", visibility = ["//visibility:public"], ) -cc_test( - name = "seobeo_websocket_test", - srcs = ["tests/seobeo_websocket_test.c"], - deps = [":seobeo_client"], - size = "small", - timeout = "short", +alias( + name = "seobeo_client", + actual = ":seobeo", + visibility = ["//visibility:public"], +) +# ============================================================================ +# Testing Utilities +# ============================================================================ + +# Testing library with snapshot and test generation tools +alias( + name = "seobeo_test", + actual = select({ + "//config:macos": ":seobeo_test_macos", + "//config:linux": ":seobeo_test_linux", + "//conditions:default": ":seobeo_test_linux", + }), visibility = ["//visibility:public"], ) -cc_test( - name = "seobeo_websocket_server_test", - srcs = ["tests/seobeo_web_server_test.c"], - deps = [":seobeo_client"], - data = [ - ":websocket_server_example", +cc_library( + name = "seobeo_test_macos", + srcs = [ + "snapshot_creator.c", + ], + hdrs = [ + "snapshot_creator.h", + ], + deps = [ + ":seobeo_tcp_client_macos", + ], + target_compatible_with = [ + "@platforms//os:osx", ], - size = "large", - timeout = "long", - args = ["$(location //seobeo:websocket_server_example)"], + visibility = ["//visibility:public"], ) + +cc_library( + name = "seobeo_test_linux", + srcs = [ + "snapshot_creator.c", + ], + hdrs = [ + "snapshot_creator.h", + ], + deps = [ + ":seobeo_tcp_client_linux", + ], + target_compatible_with = [ + "@platforms//os:linux", + ], + visibility = ["//visibility:public"], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/examples/BUILD Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,8 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") + +cc_binary( + name = "websocket_server_example", + srcs = ["websocket_server_example.c"], + deps = ["//seobeo:seobeo"], + visibility = ["//visibility:public"], +)
--- a/seobeo/examples/websocket_server_example.c Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/examples/websocket_server_example.c Fri Jan 09 07:42:04 2026 -0800 @@ -29,7 +29,7 @@ snprintf(message, sizeof(message), "[%s]: %.*s", p_conn->client_id, (int)p_msg->length, (char*)p_msg->data); printf("[Chat] Broadcasting: %s\n", message); - Seobeo_WebSocket_Server_Broadcast_Text(message); + Seobeo_WebSocket_Server_Broadcast_Text(message, p_conn); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/s_logging.c Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,30 @@ +#include "seobeo/seobeo.h" + +static char *Seobeo_Log_Level_String(Seobeo_Log_Level level) +{ + switch(level) + { + case SEOBEO_DEBUG: return "DEBUG"; + case SEOBEO_INFO: return "INFO"; + case SEOBEO_WARNING: return "WARNING"; + case SEOBEO_ERROR: return "ERROR"; + default: return "INFO"; + } +} + +int Seobeo_Log(Seobeo_Log_Level level, const char * restrict format, ...) +{ + #ifndef SEOBEO_ENABLE_DEBUG + if (level == SEOBEO_DEBUG) + return 0; + #endif + + int result; + va_list args; + printf("[%s] ", Seobeo_Log_Level_String(level)); + va_start(args, format); + result = vprintf(format, args); + va_end(args); + + return result; +}
--- a/seobeo/s_network.c Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/s_network.c Fri Jan 09 07:42:04 2026 -0800 @@ -103,6 +103,11 @@ { perror("connect"); return NULL; } freeaddrinfo(server_infos); + // Set non-blocking + int flags = fcntl(socket_fd, F_GETFL, 0); + if (flags == -1 || fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK) != 0) + { perror("fcntl"); close(socket_fd); free(p_handle); return NULL; } + p_handle->socket = socket_fd; p_handle->type = SEOBEO_STREAM_TYPE_CLIENT;
--- a/seobeo/s_web.c Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/s_web.c Fri Jan 09 07:42:04 2026 -0800 @@ -65,13 +65,14 @@ void Seobeo_Web_HandleClientRequest(Seobeo_Handle *p_cli_handle, Seobeo_Cache_Entry *p_html_cache) { - Dowa_Arena *p_request_arena = Dowa_Arena_Create(1*1024*1024); // 1MB for request parsing + // TODO: This should be splitted up instead of handling up to 50 MB as it will fail more often... + Dowa_Arena *p_request_arena = Dowa_Arena_Create(50*1024*1024); // 50 MB because of files... if (!p_request_arena) { perror("Dowa_Arena_Create request"); goto clean_up; } - Dowa_Arena *p_response_arena = Dowa_Arena_Create(5*1024*1024); // 5MB for response + Dowa_Arena *p_response_arena = Dowa_Arena_Create(50*1024*1024); // 50 MB for response if (!p_response_arena) { perror("Dowa_Arena_Create response"); goto clean_up; } - void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)1024*5); // 5Kb + void *p_response_header = Dowa_Arena_Allocate(p_response_arena, 1024*5); // 5Kb if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; } Seobeo_Request_Entry *p_req_map = NULL; @@ -135,6 +136,7 @@ const char *path = p_path_kv ? ((Seobeo_Request_Entry*)p_path_kv)->value : "/"; // --- Check for WebSocket upgrade request --- + #ifdef SEOBEO_WEBSOCKET_SERVER if (Seobeo_WebSocket_Server_Handle_Upgrade(p_cli_handle, p_req_map, path)) { Seobeo_Log(SEOBEO_INFO, "WebSocket connection established\n"); @@ -144,6 +146,7 @@ Dowa_Arena_Free(p_response_arena); return; } + #endif // --- Try to match API route first --- Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena); @@ -730,31 +733,5 @@ g_routes = NULL; } -static char *Seobeo_Log_Level_String(Seobeo_Log_Level level) -{ - switch(level) { - case SEOBEO_DEBUG: return "DEBUG"; - case SEOBEO_INFO: return "INFO"; - case SEOBEO_WARNING: return "WARNING"; - case SEOBEO_ERROR: return "ERROR"; - default: return "INFO"; - } -} +// Logging functions moved to s_logging.c -int Seobeo_Log(Seobeo_Log_Level level, const char * restrict format, ...) -{ - #ifndef SEOBEO_ENABLE_DEBUG - if (level == SEOBEO_DEBUG) - return 0; - #endif - - int result; - va_list args; - printf("[%s] ", Seobeo_Log_Level_String(level)); - va_start(args, format); - result = vprintf(format, args); - va_end(args); - - return result; -} -
--- a/seobeo/s_websocket.c Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/s_websocket.c Fri Jan 09 07:42:04 2026 -0800 @@ -190,11 +190,7 @@ return p_ws; } -static void Seobeo_WebSocket_Mask_Data(uint8 *data, size_t length, const uint8 *mask) -{ - for (size_t i = 0; i < length; i++) - data[i] ^= mask[i % 4]; -} +// Seobeo_WebSocket_Mask_Data moved to s_websocket_common.c static int32 Seobeo_WebSocket_Send_Frame(Seobeo_WebSocket *p_ws, Seobeo_WebSocket_Opcode opcode, const uint8 *payload, size_t payload_length, boolean fin) @@ -484,16 +480,7 @@ return p_msg; } -void Seobeo_WebSocket_Message_Destroy(Seobeo_WebSocket_Message *p_msg) -{ - if (!p_msg) - return; - - if (p_msg->data) - free(p_msg->data); - - free(p_msg); -} +// Seobeo_WebSocket_Message_Destroy moved to s_websocket_common.c int32 Seobeo_WebSocket_Close(Seobeo_WebSocket *p_ws, uint16 code, const char *reason) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/s_websocket_common.c Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,23 @@ +#include "seobeo/seobeo.h" +#include "seobeo/seobeo_internal.h" + +// Mask/unmask data with XOR operation (same for both directions) +void Seobeo_WebSocket_Mask_Data(uint8 *data, size_t length, const uint8 *mask) +{ + for (size_t i = 0; i < length; i++) + { + data[i] ^= mask[i % 4]; + } +} + +// Destroy a WebSocket message +void Seobeo_WebSocket_Message_Destroy(Seobeo_WebSocket_Message *p_msg) +{ + if (!p_msg) + return; + + if (p_msg->data) + free(p_msg->data); + + free(p_msg); +}
--- a/seobeo/s_websocket_server.c Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/s_websocket_server.c Fri Jan 09 07:42:04 2026 -0800 @@ -120,11 +120,7 @@ return TRUE; } -static void Seobeo_WebSocket_Unmask_Data(uint8 *data, size_t length, const uint8 *mask) -{ - for (size_t i = 0; i < length; i++) - data[i] ^= mask[i % 4]; -} +// Seobeo_WebSocket_Unmask_Data removed - using Seobeo_WebSocket_Mask_Data from s_websocket_common.c (XOR is symmetric) static int32 Seobeo_WebSocket_Server_Send_Frame(Seobeo_WebSocket_Server_Connection *p_conn, Seobeo_WebSocket_Opcode opcode, const uint8 *payload, size_t payload_length, boolean fin) { @@ -285,7 +281,7 @@ memcpy(payload, buf + header_len, payload_len); if (masked) - Seobeo_WebSocket_Unmask_Data(payload, payload_len, mask_key); + Seobeo_WebSocket_Mask_Data(payload, payload_len, mask_key); } Seobeo_Handle_Consume(p_conn->p_handle, (uint32)(header_len + payload_len)); @@ -414,7 +410,7 @@ Seobeo_WebSocket_Server_Connection_Destroy(p_conn); } -void Seobeo_WebSocket_Server_Broadcast_Text(const char *text) +void Seobeo_WebSocket_Server_Broadcast_Text(const char *text, Seobeo_WebSocket_Server_Connection *origin_p_conn) { if (!text) return; @@ -422,7 +418,7 @@ Seobeo_WebSocket_Server_Connection *p_conn = g_ws_connections; while (p_conn) { - if (p_conn->is_active) + if (p_conn->is_active && p_conn != origin_p_conn) Seobeo_WebSocket_Server_Send_Text(p_conn, text); p_conn = p_conn->next;
--- a/seobeo/seobeo.h Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/seobeo.h Fri Jan 09 07:42:04 2026 -0800 @@ -313,7 +313,7 @@ /* Send binary message to specific WebSocket client. */ extern int32 Seobeo_WebSocket_Server_Send_Binary(Seobeo_WebSocket_Server_Connection *p_conn, const uint8 *data, size_t length); /* Broadcast text message to all connected WebSocket clients. */ -extern void Seobeo_WebSocket_Server_Broadcast_Text(const char *text); +extern void Seobeo_WebSocket_Server_Broadcast_Text(const char *text, Seobeo_WebSocket_Server_Connection *origin_p_conn); /* Broadcast binary message to all connected WebSocket clients. */ extern void Seobeo_WebSocket_Server_Broadcast_Binary(const uint8 *data, size_t length); /* Close WebSocket connection with status code and reason. */
--- a/seobeo/seobeo_internal.h Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/seobeo_internal.h Fri Jan 09 07:42:04 2026 -0800 @@ -187,6 +187,10 @@ Dowa_Arena *p_arena; } Seobeo_WebSocket; +// --- WebSocket Common Functions --- // +extern void Seobeo_WebSocket_Mask_Data(uint8 *data, size_t length, const uint8 *mask); +extern void Seobeo_WebSocket_Message_Destroy(Seobeo_WebSocket_Message *p_msg); + // --- WebSocket Client Functions --- // extern Seobeo_WebSocket *Seobeo_WebSocket_Connect(const char *url); extern int32 Seobeo_WebSocket_Send_Text(Seobeo_WebSocket *p_ws, const char *text); @@ -194,7 +198,6 @@ extern int32 Seobeo_WebSocket_Send_Ping(Seobeo_WebSocket *p_ws, const char *payload); extern int32 Seobeo_WebSocket_Send_Pong(Seobeo_WebSocket *p_ws, const char *payload); extern Seobeo_WebSocket_Message *Seobeo_WebSocket_Receive(Seobeo_WebSocket *p_ws); -extern void Seobeo_WebSocket_Message_Destroy(Seobeo_WebSocket_Message *p_msg); extern int32 Seobeo_WebSocket_Close(Seobeo_WebSocket *p_ws, uint16 code, const char *reason); extern void Seobeo_WebSocket_Destroy(Seobeo_WebSocket *p_ws); @@ -233,7 +236,7 @@ extern void Seobeo_WebSocket_Server_Handle_Connection(Seobeo_WebSocket_Server_Connection *p_conn); extern int32 Seobeo_WebSocket_Server_Send_Text(Seobeo_WebSocket_Server_Connection *p_conn, const char *text); extern int32 Seobeo_WebSocket_Server_Send_Binary(Seobeo_WebSocket_Server_Connection *p_conn, const uint8 *data, size_t length); -extern void Seobeo_WebSocket_Server_Broadcast_Text(const char *text); +extern void Seobeo_WebSocket_Server_Broadcast_Text(const char *text, Seobeo_WebSocket_Server_Connection *origin_p_conn); extern void Seobeo_WebSocket_Server_Broadcast_Binary(const uint8 *data, size_t length); extern void Seobeo_WebSocket_Server_Connection_Close(Seobeo_WebSocket_Server_Connection *p_conn, uint16 code, const char *reason); extern void Seobeo_WebSocket_Server_Connection_Destroy(Seobeo_WebSocket_Server_Connection *p_conn);
--- a/seobeo/snapshot_creator.c Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/snapshot_creator.c Fri Jan 09 07:42:04 2026 -0800 @@ -1,13 +1,11 @@ #include "seobeo/snapshot_creator.h" +#include "seobeo/seobeo_internal.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> -#define MAX_RESPONSE_SIZE (1024 * 1024) - -// Helper: Convert URL path to filename static void path_to_filename(const char *path, char *filename, size_t max_len) { if (strcmp(path, "/") == 0) @@ -18,9 +16,7 @@ const char *p = path; if (*p == '/') - { p++; - } char *out = filename; size_t remaining = max_len - 1; @@ -69,68 +65,61 @@ return mkdir(tmp, 0755); } -// Helper: Generate HTTP GET request -static int generate_http_get(char *buffer, size_t size, const char *path, const char *host) +// Helper: Build full HTTP response from Seobeo_Client_Response +static char* build_full_response(Seobeo_Client_Response *p_resp, size_t *len_out) { - return snprintf( - buffer, size, - "GET %s HTTP/1.1\r\n" - "Host: %s\r\n" - "Connection: close\r\n" - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" - "User-Agent: SeobeoSnapshotCreator/1.0\r\n" - "\r\n", - path, host - ); -} - -// Helper: Read full HTTP response -static char* read_full_response(Seobeo_Handle *client, size_t *len_out) -{ - char *response = malloc(MAX_RESPONSE_SIZE); - if (!response) - { + if (!p_resp) return NULL; - } - - size_t total = 0; - int attempts = 0; - const int max_attempts = 100; - while (attempts++ < max_attempts && total < MAX_RESPONSE_SIZE - 1) - { - int n = Seobeo_Handle_Read(client); - - if (n > 0) - { - size_t to_copy = client->read_buffer_len; - if (total + to_copy > MAX_RESPONSE_SIZE - 1) - { - to_copy = MAX_RESPONSE_SIZE - 1 - total; - } + // Calculate total response size + size_t status_line_len = 100; // "HTTP/1.1 200 OK\r\n" + size_t headers_len = 0; + size_t body_len = p_resp->body_length; - memcpy(response + total, client->read_buffer, to_copy); - total += to_copy; - Seobeo_Handle_Consume(client, (uint32)to_copy); - } - else if (n == -2) + // Estimate header size + if (p_resp->headers) + { + size_t header_count = Dowa_Array_Length(p_resp->headers); + for (size_t i = 0; i < header_count; i++) { - break; - } - else if (n == 0) - { - usleep(10000); - continue; - } - else - { - free(response); - return NULL; + headers_len += strlen(p_resp->headers[i].key) + strlen(p_resp->headers[i].value) + 4; // ": " + "\r\n" } } - response[total] = '\0'; - *len_out = total; + size_t total_size = status_line_len + headers_len + 2 + body_len + 1; // +2 for "\r\n", +1 for null terminator + char *response = malloc(total_size); + if (!response) + return NULL; + + // Build status line + int offset = snprintf(response, total_size, "HTTP/1.1 %d %s\r\n", + p_resp->status_code, + p_resp->status_text ? p_resp->status_text : "OK"); + + // Add headers + if (p_resp->headers) + { + size_t header_count = Dowa_Array_Length(p_resp->headers); + for (size_t i = 0; i < header_count; i++) + { + offset += snprintf(response + offset, total_size - offset, "%s: %s\r\n", + p_resp->headers[i].key, + p_resp->headers[i].value); + } + } + + // End of headers + offset += snprintf(response + offset, total_size - offset, "\r\n"); + + // Add body + if (p_resp->body && body_len > 0) + { + memcpy(response + offset, p_resp->body, body_len); + offset += body_len; + } + + response[offset] = '\0'; + *len_out = offset; return response; } @@ -171,66 +160,39 @@ printf("Creating snapshot: %s (expecting %d)\n", config->path, config->expected_status); - // Connect to server - Seobeo_Handle *client = Seobeo_Stream_Handle_Client_Create(config->host, config->port, FALSE); - if (!client || client->socket < 0) + // Build URL + char url[2048]; + snprintf(url, sizeof(url), "http://%s:%s%s", config->host, config->port, config->path); + + // Create HTTP request + Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(url); + if (!p_req) { - fprintf(stderr, " ✗ Failed to connect to %s:%s\n", config->host, config->port); - if (client) - { - Seobeo_Handle_Destroy(client); - } - return -1; - } - - // Send GET request - char request[4096]; - int req_len = generate_http_get(request, sizeof(request), config->path, config->host); - Seobeo_Handle_Queue(client, (uint8*)request, (uint32)req_len); - - if (Seobeo_Handle_Flush(client) < 0) - { - fprintf(stderr, " ✗ Failed to send request\n"); - Seobeo_Handle_Destroy(client); + fprintf(stderr, " ✗ Failed to create request for %s\n", url); return -1; } - // Read response - size_t response_len = 0; - char *response = read_full_response(client, &response_len); - Seobeo_Handle_Destroy(client); - - if (!response || response_len == 0) + // Execute request + Seobeo_Client_Response *p_resp = Seobeo_Client_Request_Execute(p_req); + if (!p_resp) { - fprintf(stderr, " ✗ Failed to read response\n"); - if (response) - { - free(response); - } + fprintf(stderr, " ✗ Failed to get response\n"); + Seobeo_Client_Request_Destroy(p_req); return -1; } - // Parse status code - int status = -1; - const char *status_line = strstr(response, "HTTP/1.1 "); - if (!status_line) - { - status_line = strstr(response, "HTTP/1.0 "); - } - if (status_line) - { - sscanf(status_line + 9, "%d", &status); - } - - if (status != config->expected_status) + // Check status code + if (p_resp->status_code != config->expected_status) { fprintf(stderr, " ✗ Status mismatch: expected %d, got %d\n", - config->expected_status, status); - free(response); + config->expected_status, p_resp->status_code); + // fprintf(stderr, "%s", p_resp->body); + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); return -1; } - printf(" ✓ Status: %d\n", status); + printf(" ✓ Status: %d\n", p_resp->status_code); // Generate snapshot filename char filename[256]; @@ -240,18 +202,19 @@ snprintf(filepath, sizeof(filepath), "%s/%s", config->snapshot_dir, filename); // Write snapshot - if (write_snapshot(filepath, response, response_len) == 0) + if (write_snapshot(filepath, p_resp->body, p_resp->body_length) == 0) { - printf(" ✓ Snapshot saved: %s (%zu bytes)\n", filepath, response_len); - free(response); + printf(" ✓ Snapshot saved: %s (%zu bytes)\n", filepath, p_resp->body_length); return 0; } else { fprintf(stderr, " ✗ Failed to write snapshot: %s\n", filepath); - free(response); return -1; } + + Seobeo_Client_Response_Destroy(p_resp); + Seobeo_Client_Request_Destroy(p_req); } int Seobeo_Snapshots_Create_Batch(const SnapshotConfig configs[], int count) @@ -262,13 +225,9 @@ for (int i = 0; i < count; i++) { if (Seobeo_Snapshot_Create(&configs[i]) == 0) - { passed++; - } else - { failed++; - } printf("\n"); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/tests/BUILD Fri Jan 09 07:42:04 2026 -0800 @@ -0,0 +1,30 @@ +load("@rules_cc//cc:cc_test.bzl", "cc_test") + +cc_test( + name = "seobeo_client_test", + srcs = ["seobeo_client_test.c"], + deps = ["//seobeo:seobeo"], + visibility = ["//visibility:public"], +) + +cc_test( + name = "seobeo_websocket_test", + srcs = ["seobeo_websocket_test.c"], + deps = ["//seobeo:seobeo"], + size = "small", + timeout = "short", + visibility = ["//visibility:public"], +) + +cc_test( + name = "seobeo_websocket_server_test", + srcs = ["seobeo_web_server_test.c"], + deps = ["//seobeo:seobeo"], + data = [ + "//seobeo/examples:websocket_server_example", + ], + size = "large", + timeout = "long", + args = ["$(location //seobeo/examples:websocket_server_example)"], + visibility = ["//visibility:public"], +)
--- a/seobeo/tests/seobeo_web_server_test.c Fri Jan 09 07:19:09 2026 -0800 +++ b/seobeo/tests/seobeo_web_server_test.c Fri Jan 09 07:42:04 2026 -0800 @@ -12,8 +12,8 @@ if (server_pid == 0) { - printf("Starting server on port 8000...\n"); - execlp(server_binary, NULL); + printf("Starting server on port 8080...\n"); + execl(server_binary, server_binary, NULL); perror("execl failed"); exit(1); }