diff mrjunejune/main.c @ 201:6cdee35a7ba9

[MrJuneJune] notes
author MrJuneJune <me@mrjunejune.com>
date Sun, 15 Feb 2026 07:07:50 -0800
parents 90dfcef375fb
children b9b184b3303c
line wrap: on
line diff
--- a/mrjunejune/main.c	Sat Feb 14 16:32:24 2026 -0800
+++ b/mrjunejune/main.c	Sun Feb 15 07:07:50 2026 -0800
@@ -1,7 +1,11 @@
 #include "seobeo/seobeo.h"
 #include "markdown_converter/markdown_to_html.h"
 #include "s3/s3_uploader.h"
+#include "deita/deita.h"
 #include <time.h>
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <pthread.h>
 
 // UUID + /tmp/ + format (max 4)
 #define TMP_FILE_LENGTH 47
@@ -10,12 +14,25 @@
 volatile sig_atomic_t stop_server = 0;
 static _Atomic uint32_t counter = 0;
 
+// Media Processing Context for background threads
+typedef struct {
+  int64    media_id;
+  char     s3_key_original[512];
+  char     s3_key_processed[512];
+  char     content_type[128];
+  char     access_token[256];
+  S3_Config s3_config;
+} Media_Processing_Context;
+
 // Server configuration (loaded from .config)
 static char g_upload_auth_token[256] = {0};
 static char g_s3_region[64] = "us-west-2";
 static char g_s3_bucket[128] = "mrjunejune";
+static char g_s3_cloudfront_url[256] = {0};
+static char g_db_path[256] = "mrjunejune/data/mrjunejune.db";
 static int  g_s3_url_expires = 3600;
 static S3_Config g_s3_config = {0};
+static Deita_Connection *g_db_connection = NULL;
 
 static void load_config(const char *config_path)
 {
@@ -60,12 +77,105 @@
     {
       g_s3_url_expires = atoi(value);
     }
+    else if (strcmp(key, "S3_CLOUDFRONT_URL") == 0)
+    {
+      strncpy(g_s3_cloudfront_url, value, sizeof(g_s3_cloudfront_url) - 1);
+    }
+    else if (strcmp(key, "DB_PATH") == 0)
+    {
+      strncpy(g_db_path, value, sizeof(g_db_path) - 1);
+    }
   }
   fclose(f);
 
-  printf("[CONFIG] Loaded: token=%s..., region=%s, bucket=%s, expires=%d\n",
+  printf("[CONFIG] Loaded: token=%s..., region=%s, bucket=%s, expires=%d, cloudfront=%s, db=%s\n",
          g_upload_auth_token[0] ? "***" : "(empty)",
-         g_s3_region, g_s3_bucket, g_s3_url_expires);
+         g_s3_region, g_s3_bucket, g_s3_url_expires,
+         g_s3_cloudfront_url[0] ? g_s3_cloudfront_url : "(none)",
+         g_db_path);
+}
+
+static void init_database(void)
+{
+  // Create data directory if needed
+  char *last_slash = strrchr(g_db_path, '/');
+  if (last_slash)
+  {
+    char dir_path[256];
+    size_t dir_len = last_slash - g_db_path;
+    strncpy(dir_path, g_db_path, dir_len);
+    dir_path[dir_len] = '\0';
+    mkdir(dir_path, 0755);
+  }
+
+  g_db_connection = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, g_db_path);
+  if (!g_db_connection || !Deita_Connection_Is_Open(g_db_connection))
+  {
+    printf("[DB] ERROR: Failed to open database at %s\n", g_db_path);
+    return;
+  }
+
+  // Create editor_content table
+  const char *create_table =
+    "CREATE TABLE IF NOT EXISTS editor_content ("
+    "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
+    "  access_token TEXT NOT NULL,"
+    "  doc_id TEXT NOT NULL,"
+    "  content TEXT,"
+    "  created_at INTEGER DEFAULT (strftime('%s', 'now')),"
+    "  updated_at INTEGER DEFAULT (strftime('%s', 'now')),"
+    "  UNIQUE(access_token, doc_id)"
+    ")";
+
+  int32 result = Deita_Query_Execute_Update(g_db_connection, create_table);
+  if (result < 0)
+  {
+    printf("[DB] ERROR: Failed to create editor_content table\n");
+  }
+
+  // Create media_uploads table
+  const char *create_media_uploads =
+    "CREATE TABLE IF NOT EXISTS media_uploads ("
+    "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
+    "  access_token TEXT NOT NULL,"
+    "  original_filename TEXT NOT NULL,"
+    "  content_type TEXT NOT NULL,"
+    "  s3_key_original TEXT NOT NULL,"
+    "  s3_key_processed TEXT,"
+    "  file_size INTEGER,"
+    "  status TEXT NOT NULL DEFAULT 'pending',"
+    "  error_message TEXT,"
+    "  created_at INTEGER DEFAULT (strftime('%s', 'now')),"
+    "  updated_at INTEGER DEFAULT (strftime('%s', 'now'))"
+    ")";
+
+  result = Deita_Query_Execute_Update(g_db_connection, create_media_uploads);
+  if (result < 0)
+  {
+    printf("[DB] ERROR: Failed to create media_uploads table\n");
+  }
+
+  // Create indices for media_uploads
+  const char *create_status_idx =
+    "CREATE INDEX IF NOT EXISTS idx_media_uploads_status ON media_uploads(status)";
+  result = Deita_Query_Execute_Update(g_db_connection, create_status_idx);
+  if (result < 0)
+  {
+    printf("[DB] ERROR: Failed to create status index\n");
+  }
+
+  const char *create_token_status_idx =
+    "CREATE INDEX IF NOT EXISTS idx_media_uploads_token_status "
+    "ON media_uploads(access_token, status)";
+  result = Deita_Query_Execute_Update(g_db_connection, create_token_status_idx);
+  if (result < 0)
+  {
+    printf("[DB] ERROR: Failed to create token_status index\n");
+  }
+  else
+  {
+    printf("[DB] Initialized: %s\n", g_db_path);
+  }
 }
 
 void handle_sigint(int sig)
@@ -93,6 +203,8 @@
     char *end_tag = strstr(start_tag, "}}");
     if (!end_tag) break;
 
+    Seobeo_Log(SEOBEO_INFO, "[Curr] Life\n");
+
     size_t leading_len = start_tag - cursor;
     memcpy(final_body + current_offset, cursor, leading_len);
     current_offset += leading_len;
@@ -104,7 +216,8 @@
 
     size_t sub_file_size = 0;
     char *sub_content = Seobeo_Web_LoadFile(include_name, &sub_file_size);
-    Seobeo_Log(SEOBEO_DEBUG, "[Curr] Sub content: %s\n", sub_content);
+    Seobeo_Log(SEOBEO_DEBUG, "[TEMPLATE] Loading include: '%s' -> %s (size=%zu)\n",
+              include_name, sub_content ? "OK" : "FAILED", sub_file_size);
     if (sub_content)
     {
       memcpy(final_body + current_offset, sub_content, sub_file_size);
@@ -117,15 +230,15 @@
   strcpy(final_body + current_offset, cursor);
 }
 
-
 void Seobeo_Render_Html_FilePath(
     char *final_body,
     char *path,
     Dowa_Arena *arena
 ) {
-  Seobeo_Log(SEOBEO_DEBUG, "[Curr] %s\n", path);
+  Seobeo_Log(SEOBEO_DEBUG, "[TEMPLATE] Loading main template: '%s'\n", path);
   size_t html_size = 0;
   char *template = Seobeo_Web_LoadFile(path, &html_size);
+  Seobeo_Log(SEOBEO_DEBUG, "[TEMPLATE] Main template loaded: %s (size=%zu)\n", template ? "OK" : "FAILED", html_size);
   if (!template) return;
   Seobeo_Render_Html(final_body, template, arena);
 }
@@ -182,7 +295,7 @@
 
   if (!req)
   {
-    printf("ERROR: Request is NULL\n");
+    Seobeo_Log(SEOBEO_ERROR, "Request is NULL\n");
     char *error_msg = "Internal error: no request data";
     Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
     Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
@@ -195,7 +308,7 @@
 
   for (size_t i = 0; i < req_length; i++)
   {
-    printf("  Key[%zu]: '%s'\n", i, req[i].key);
+    Seobeo_Log(SEOBEO_INFO, "  Key[%zu]: '%s'\n", i, req[i].key);
   }
 
   void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
@@ -555,12 +668,50 @@
   return resp;
 }
 
+Seobeo_Request_Entry *GetEditor(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
+  Seobeo_Render_Html_FilePath(final_body, "/editor/index.html", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
+  return resp;
+}
+
+Seobeo_Request_Entry *GetNotesLogin(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
+  Seobeo_Render_Html_FilePath(final_body, "/notes/login.html", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
+  return resp;
+}
+
+Seobeo_Request_Entry *GetNotes(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
+  Seobeo_Render_Html_FilePath(final_body, "/notes/index.html", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
+  return resp;
+}
+
+Seobeo_Request_Entry *GetNoteById(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
+  // Same template - JavaScript handles the note_id from URL
+  Seobeo_Render_Html_FilePath(final_body, "/notes/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")
+CREATE_REDIRECT_HANDLER(Editor, "/editor")
 
 // S3 Upload URL API
 // POST /api/s3/upload-url
@@ -681,11 +832,23 @@
     return resp;
   }
 
+  // Build public URL using CloudFront
+  char public_url[512];
+  if (g_s3_cloudfront_url[0])
+  {
+    snprintf(public_url, sizeof(public_url), "%s/%s", g_s3_cloudfront_url, s3_key);
+  }
+  else
+  {
+    snprintf(public_url, sizeof(public_url), "https://%s.s3.%s.amazonaws.com/%s",
+             g_s3_bucket, g_s3_region, s3_key);
+  }
+
   // Build response
-  char *response_body = Dowa_Arena_Allocate(arena, 2048 + strlen(presigned.url));
-  snprintf(response_body, 2048 + strlen(presigned.url),
-           "{\"upload_url\":\"%s\",\"key\":\"%s\",\"expires\":%d}",
-           presigned.url, s3_key, g_s3_url_expires);
+  char *response_body = Dowa_Arena_Allocate(arena, 4096 + strlen(presigned.url));
+  snprintf(response_body, 4096 + strlen(presigned.url),
+           "{\"upload_url\":\"%s\",\"public_url\":\"%s\",\"key\":\"%s\",\"expires\":%d}",
+           presigned.url, public_url, s3_key, g_s3_url_expires);
 
   S3_Presigned_URL_Destroy(&presigned);
 
@@ -698,6 +861,816 @@
   return resp;
 }
 
+// Editor Content Save API
+// POST /api/editor/save
+// Headers: Authorization: Bearer <token>
+// Body: {"doc_id": "my-doc", "content": "<html content>"}
+Seobeo_Request_Entry *EditorSave(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+
+  // Check auth token
+  void *auth_kv = Dowa_HashMap_Get_Ptr(req, "Authorization");
+  if (!auth_kv)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing Authorization header\"}", arena);
+    return resp;
+  }
+
+  const char *auth_header = ((Seobeo_Request_Entry*)auth_kv)->value;
+  if (strncmp(auth_header, "Bearer ", 7) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid Authorization format\"}", arena);
+    return resp;
+  }
+
+  const char *token = auth_header + 7;
+  if (strlen(g_upload_auth_token) == 0 || strcmp(token, g_upload_auth_token) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "403", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid token\"}", arena);
+    return resp;
+  }
+
+  if (!g_db_connection)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Database not available\"}", arena);
+    return resp;
+  }
+
+  // Parse request body
+  void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
+  if (!body_kv)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing request body\"}", arena);
+    return resp;
+  }
+
+  const char *body = ((Seobeo_Request_Entry*)body_kv)->value;
+
+  // Parse doc_id and content from JSON
+  char doc_id[256] = "default";
+  char *content = NULL;
+  size_t content_len = 0;
+
+  // Find "doc_id":"value"
+  const char *doc_key = strstr(body, "\"doc_id\"");
+  if (doc_key)
+  {
+    const char *doc_start = strchr(doc_key + 8, '"');
+    if (doc_start)
+    {
+      doc_start++;
+      const char *doc_end = strchr(doc_start, '"');
+      if (doc_end && (size_t)(doc_end - doc_start) < sizeof(doc_id))
+      {
+        memcpy(doc_id, doc_start, doc_end - doc_start);
+        doc_id[doc_end - doc_start] = '\0';
+      }
+    }
+  }
+
+  // Find "content":"value" - content can be large and contain escaped characters
+  const char *content_key = strstr(body, "\"content\"");
+  if (content_key)
+  {
+    const char *content_start = strchr(content_key + 9, '"');
+    if (content_start)
+    {
+      content_start++;
+      // Find closing quote (accounting for escaped quotes)
+      const char *p = content_start;
+      while (*p)
+      {
+        if (*p == '\\' && *(p+1))
+        {
+          p += 2;
+          continue;
+        }
+        if (*p == '"') break;
+        p++;
+      }
+      content_len = p - content_start;
+      content = Dowa_Arena_Allocate(arena, content_len + 1);
+      memcpy(content, content_start, content_len);
+      content[content_len] = '\0';
+    }
+  }
+
+  if (!content)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing content\"}", arena);
+    return resp;
+  }
+
+  // Upsert content
+  const char *upsert_query =
+    "INSERT INTO editor_content (access_token, doc_id, content, updated_at) "
+    "VALUES (?, ?, ?, strftime('%s', 'now')) "
+    "ON CONFLICT(access_token, doc_id) DO UPDATE SET "
+    "content = excluded.content, updated_at = strftime('%s', 'now')";
+
+  const char *params[] = { token, doc_id, content };
+  int32 result = Deita_Query_Execute_Update_Prepared(g_db_connection, upsert_query, 3, params);
+
+  if (result < 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Failed to save\"}", arena);
+    return resp;
+  }
+
+  printf("[EDITOR] Saved doc_id=%s, content_len=%zu\n", doc_id, content_len);
+
+  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", "{\"success\":true}", arena);
+  return resp;
+}
+
+// Editor Content Load API
+// GET /api/editor/load/:doc_id
+// Headers: Authorization: Bearer <token>
+Seobeo_Request_Entry *EditorLoad(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+
+  // Check auth token
+  void *auth_kv = Dowa_HashMap_Get_Ptr(req, "Authorization");
+  if (!auth_kv)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing Authorization header\"}", arena);
+    return resp;
+  }
+
+  const char *auth_header = ((Seobeo_Request_Entry*)auth_kv)->value;
+  if (strncmp(auth_header, "Bearer ", 7) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid Authorization format\"}", arena);
+    return resp;
+  }
+
+  const char *token = auth_header + 7;
+  if (strlen(g_upload_auth_token) == 0 || strcmp(token, g_upload_auth_token) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "403", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid token\"}", arena);
+    return resp;
+  }
+
+  if (!g_db_connection)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Database not available\"}", arena);
+    return resp;
+  }
+
+  // Get doc_id from URL parameter
+  void *doc_id_kv = Dowa_HashMap_Get_Ptr(req, ":doc_id");
+  const char *doc_id = "default";
+  if (doc_id_kv)
+  {
+    doc_id = ((Seobeo_Request_Entry*)doc_id_kv)->value;
+  }
+
+  // Query content
+  const char *select_query =
+    "SELECT content, updated_at FROM editor_content WHERE access_token = ? AND doc_id = ?";
+  const char *params[] = { token, doc_id };
+
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(g_db_connection, select_query, 2, params, arena);
+
+  if (p_result && Deita_Result_Set_Next(p_result))
+  {
+    const char *content = Deita_Result_Set_Get_Text(p_result, 0);
+    int64 updated_at = Deita_Result_Set_Get_Integer(p_result, 1);
+
+    // Build JSON response - escape content
+    size_t content_len = content ? strlen(content) : 0;
+    char *response_body = Dowa_Arena_Allocate(arena, content_len + 256);
+    snprintf(response_body, content_len + 256,
+             "{\"doc_id\":\"%s\",\"content\":\"%s\",\"updated_at\":%lld}",
+             doc_id, content ? content : "", (long long)updated_at);
+
+    Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", response_body, arena);
+
+    printf("[EDITOR] Loaded doc_id=%s\n", doc_id);
+  }
+  else
+  {
+    // No content found, return empty
+    char *response_body = Dowa_Arena_Allocate(arena, 128);
+    snprintf(response_body, 128, "{\"doc_id\":\"%s\",\"content\":\"\",\"updated_at\":0}", doc_id);
+
+    Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", response_body, arena);
+  }
+
+  if (p_result) Deita_Result_Set_Free(p_result);
+  return resp;
+}
+
+// Media Upload API - Create media record
+// POST /api/media/create
+// Headers: Authorization: Bearer <token>, Content-Type: application/json
+// Body: {"filename": "photo.jpg", "content_type": "image/jpeg"}
+// Returns: {"media_id": 123, "upload_url": "https://...", "expires": 3600}
+Seobeo_Request_Entry *MediaCreate(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+
+  // Check auth token
+  void *auth_kv = Dowa_HashMap_Get_Ptr(req, "Authorization");
+  if (!auth_kv)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing Authorization header\"}", arena);
+    return resp;
+  }
+
+  const char *auth_header = ((Seobeo_Request_Entry*)auth_kv)->value;
+  if (strncmp(auth_header, "Bearer ", 7) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid Authorization format\"}", arena);
+    return resp;
+  }
+
+  const char *token = auth_header + 7;
+  if (strlen(g_upload_auth_token) == 0 || strcmp(token, g_upload_auth_token) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "403", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid token\"}", arena);
+    return resp;
+  }
+
+  if (!g_db_connection)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Database not available\"}", arena);
+    return resp;
+  }
+
+  // Parse request body
+  void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
+  if (!body_kv)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing request body\"}", arena);
+    return resp;
+  }
+
+  const char *body = ((Seobeo_Request_Entry*)body_kv)->value;
+
+  // Parse filename and content_type
+  char filename[256] = {0};
+  char content_type[128] = "application/octet-stream";
+
+  // Find "filename":"value"
+  const char *fn_key = strstr(body, "\"filename\"");
+  if (fn_key)
+  {
+    const char *fn_start = strchr(fn_key + 10, '"');
+    if (fn_start)
+    {
+      fn_start++;
+      const char *fn_end = strchr(fn_start, '"');
+      if (fn_end && (size_t)(fn_end - fn_start) < sizeof(filename))
+      {
+        memcpy(filename, fn_start, fn_end - fn_start);
+        filename[fn_end - fn_start] = '\0';
+      }
+    }
+  }
+
+  // Find "content_type":"value"
+  const char *ct_key = strstr(body, "\"content_type\"");
+  if (ct_key)
+  {
+    const char *ct_start = strchr(ct_key + 14, '"');
+    if (ct_start)
+    {
+      ct_start++;
+      const char *ct_end = strchr(ct_start, '"');
+      if (ct_end && (size_t)(ct_end - ct_start) < sizeof(content_type))
+      {
+        memcpy(content_type, ct_start, ct_end - ct_start);
+        content_type[ct_end - ct_start] = '\0';
+      }
+    }
+  }
+
+  if (strlen(filename) == 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing filename\"}", arena);
+    return resp;
+  }
+
+  // Generate UUID for this upload
+  char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN);
+  uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
+  Dowa_String_UUID(seed, uuid);
+
+  // Generate S3 keys
+  char s3_key_original[512];
+  char s3_key_processed[512];
+  snprintf(s3_key_original, sizeof(s3_key_original), "uploads/%s/%s", uuid, filename);
+  snprintf(s3_key_processed, sizeof(s3_key_processed), "uploads/%s/processed.webp", uuid);
+
+  // Insert into database
+  const char *insert_query =
+    "INSERT INTO media_uploads (access_token, original_filename, content_type, s3_key_original, s3_key_processed, status) "
+    "VALUES (?, ?, ?, ?, ?, 'pending')";
+
+  const char *params[] = { token, filename, content_type, s3_key_original, s3_key_processed };
+  int32 result = Deita_Query_Execute_Update_Prepared(g_db_connection, insert_query, 5, params);
+
+  if (result < 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Failed to create media record\"}", arena);
+    return resp;
+  }
+
+  // Get the inserted media_id using last_insert_rowid()
+  const char *last_id_query = "SELECT last_insert_rowid()";
+  Deita_Result_Set *id_result = Deita_Query_Execute(g_db_connection, last_id_query, arena);
+  int64 media_id = 0;
+  if (id_result && Deita_Result_Set_Next(id_result))
+  {
+    media_id = Deita_Result_Set_Get_Integer(id_result, 0);
+  }
+  if (id_result) Deita_Result_Set_Free(id_result);
+
+  // Generate presigned PUT URL
+  S3_Presigned_URL presigned = S3_Presign_Put(&g_s3_config, s3_key_original, content_type, g_s3_url_expires);
+
+  if (!presigned.success)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    char *error_body = Dowa_Arena_Allocate(arena, 256);
+    snprintf(error_body, 256, "{\"error\":\"Failed to generate upload URL: %s\"}",
+             presigned.error_message ? presigned.error_message : "unknown");
+    Dowa_HashMap_Push_Arena(resp, "body", error_body, arena);
+    S3_Presigned_URL_Destroy(&presigned);
+    return resp;
+  }
+
+  // Build response
+  char *response_body = Dowa_Arena_Allocate(arena, 4096 + strlen(presigned.url));
+  snprintf(response_body, 4096 + strlen(presigned.url),
+           "{\"media_id\":%lld,\"upload_url\":\"%s\",\"expires\":%d}",
+           (long long)media_id, presigned.url, g_s3_url_expires);
+
+  S3_Presigned_URL_Destroy(&presigned);
+
+  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", response_body, arena);
+
+  printf("[MEDIA] Created media_id=%lld, file=%s\n", (long long)media_id, filename);
+
+  return resp;
+}
+
+// Background thread function for media processing
+void *Media_Process_Background(void *arg)
+{
+  Media_Processing_Context *ctx = (Media_Processing_Context *)arg;
+
+  // Open thread-local DB connection
+  Deita_Connection *db_conn = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, g_db_path);
+  if (!db_conn || !Deita_Connection_Is_Open(db_conn))
+  {
+    printf("[MEDIA] Thread ERROR: Failed to open database for media_id=%lld\n", (long long)ctx->media_id);
+    free(ctx);
+    return NULL;
+  }
+
+  // Update status to 'processing'
+  const char *update_processing =
+    "UPDATE media_uploads SET status='processing', updated_at=strftime('%s','now') WHERE id=?";
+  char media_id_str[32];
+  snprintf(media_id_str, sizeof(media_id_str), "%lld", (long long)ctx->media_id);
+  const char *params[] = { media_id_str };
+  Deita_Query_Execute_Update_Prepared(db_conn, update_processing, 1, params);
+
+  printf("[MEDIA] Processing media_id=%lld\n", (long long)ctx->media_id);
+
+  // Generate presigned GET URL for download (10 min expiry)
+  S3_Presigned_URL download_url = S3_Presign_Get(&ctx->s3_config, ctx->s3_key_original, 600);
+  if (!download_url.success)
+  {
+    const char *update_error =
+      "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?";
+    const char *error_params[] = { "Failed to generate download URL", media_id_str };
+    Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params);
+    printf("[MEDIA] ERROR: Failed to generate download URL for media_id=%lld\n", (long long)ctx->media_id);
+    S3_Presigned_URL_Destroy(&download_url);
+    Deita_Connection_Close(db_conn);
+    free(ctx);
+    return NULL;
+  }
+
+  // Generate temp file paths
+  char tmp_input[256];
+  char tmp_output[256];
+  char *uuid_input = malloc(UUID_LEN);
+  char *uuid_output = malloc(UUID_LEN);
+  uint32 seed1 = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
+  uint32 seed2 = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
+  Dowa_String_UUID(seed1, uuid_input);
+  Dowa_String_UUID(seed2, uuid_output);
+  snprintf(tmp_input, sizeof(tmp_input), "/tmp/%s", uuid_input);
+  snprintf(tmp_output, sizeof(tmp_output), "/tmp/%s.webp", uuid_output);
+  free(uuid_input);
+  free(uuid_output);
+
+  // Download from S3
+  Seobeo_Client_Request *download_req = Seobeo_Client_Request_Create(download_url.url);
+  Seobeo_Client_Request_Set_Download_Path(download_req, tmp_input);
+  Seobeo_Client_Response *download_resp = Seobeo_Client_Request_Execute(download_req);
+
+  S3_Presigned_URL_Destroy(&download_url);
+
+  if (!download_resp || download_resp->status_code != 200)
+  {
+    const char *update_error =
+      "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?";
+    const char *error_params[] = { "Failed to download from S3", media_id_str };
+    Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params);
+    printf("[MEDIA] ERROR: Failed to download from S3 for media_id=%lld\n", (long long)ctx->media_id);
+    if (download_req) Seobeo_Client_Request_Destroy(download_req);
+    if (download_resp) Seobeo_Client_Response_Destroy(download_resp);
+    unlink(tmp_input);
+    Deita_Connection_Close(db_conn);
+    free(ctx);
+    return NULL;
+  }
+
+  Seobeo_Client_Request_Destroy(download_req);
+  Seobeo_Client_Response_Destroy(download_resp);
+
+  // Convert to webp using FFmpeg
+  char cmd[1024];
+  char log_file[256];
+  snprintf(log_file, sizeof(log_file), "/tmp/ffmpeg_%lld.log", (long long)ctx->media_id);
+  snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>%s",
+           tmp_input, tmp_output, log_file);
+
+  int ffmpeg_result = system(cmd);
+  if (ffmpeg_result != 0)
+  {
+    const char *update_error =
+      "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?";
+    const char *error_params[] = { "Image conversion failed", media_id_str };
+    Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params);
+    printf("[MEDIA] ERROR: FFmpeg conversion failed for media_id=%lld\n", (long long)ctx->media_id);
+    unlink(tmp_input);
+    unlink(tmp_output);
+    Deita_Connection_Close(db_conn);
+    free(ctx);
+    return NULL;
+  }
+
+  // Upload processed file to S3
+  S3_Result upload_result = S3_Upload_File_With_Content_Type(
+    &ctx->s3_config, tmp_output, ctx->s3_key_processed, "image/webp");
+
+  if (!upload_result.success)
+  {
+    const char *update_error =
+      "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?";
+    const char *error_params[] = { "Failed to upload processed file", media_id_str };
+    Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params);
+    printf("[MEDIA] ERROR: Failed to upload processed file for media_id=%lld\n", (long long)ctx->media_id);
+    unlink(tmp_input);
+    unlink(tmp_output);
+    Deita_Connection_Close(db_conn);
+    free(ctx);
+    return NULL;
+  }
+
+  // Update status to 'finished'
+  const char *update_finished =
+    "UPDATE media_uploads SET status='finished', updated_at=strftime('%s','now') WHERE id=?";
+  Deita_Query_Execute_Update_Prepared(db_conn, update_finished, 1, params);
+
+  printf("[MEDIA] Successfully processed media_id=%lld\n", (long long)ctx->media_id);
+
+  // Cleanup
+  unlink(tmp_input);
+  unlink(tmp_output);
+  Deita_Connection_Close(db_conn);
+  free(ctx);
+
+  return NULL;
+}
+
+// Media Upload API - Mark uploaded
+// POST /api/media/:id/uploaded
+// Headers: Authorization: Bearer <token>
+Seobeo_Request_Entry *MediaUploaded(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+
+  // Check auth token
+  void *auth_kv = Dowa_HashMap_Get_Ptr(req, "Authorization");
+  if (!auth_kv)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing Authorization header\"}", arena);
+    return resp;
+  }
+
+  const char *auth_header = ((Seobeo_Request_Entry*)auth_kv)->value;
+  if (strncmp(auth_header, "Bearer ", 7) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid Authorization format\"}", arena);
+    return resp;
+  }
+
+  const char *token = auth_header + 7;
+  if (strlen(g_upload_auth_token) == 0 || strcmp(token, g_upload_auth_token) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "403", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid token\"}", arena);
+    return resp;
+  }
+
+  if (!g_db_connection)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Database not available\"}", arena);
+    return resp;
+  }
+
+  // Extract media_id from URL params
+  void *id_kv = Dowa_HashMap_Get_Ptr(req, ":id");
+  if (!id_kv)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing media ID\"}", arena);
+    return resp;
+  }
+
+  const char *media_id_str = ((Seobeo_Request_Entry*)id_kv)->value;
+  int64 media_id = atoll(media_id_str);
+
+  // Verify access_token matches and get content_type
+  const char *select_query =
+    "SELECT content_type, s3_key_original, s3_key_processed FROM media_uploads WHERE id = ? AND access_token = ?";
+  const char *select_params[] = { media_id_str, token };
+
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(g_db_connection, select_query, 2, select_params, arena);
+
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result) Deita_Result_Set_Free(p_result);
+    Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Media not found or access denied\"}", arena);
+    return resp;
+  }
+
+  const char *content_type = Deita_Result_Set_Get_Text(p_result, 0);
+  const char *s3_key_original = Deita_Result_Set_Get_Text(p_result, 1);
+  const char *s3_key_processed = Deita_Result_Set_Get_Text(p_result, 2);
+
+  // Copy values before freeing result set
+  char content_type_copy[128];
+  char s3_key_original_copy[512];
+  char s3_key_processed_copy[512];
+  strncpy(content_type_copy, content_type, sizeof(content_type_copy) - 1);
+  strncpy(s3_key_original_copy, s3_key_original, sizeof(s3_key_original_copy) - 1);
+  strncpy(s3_key_processed_copy, s3_key_processed, sizeof(s3_key_processed_copy) - 1);
+  content_type_copy[sizeof(content_type_copy) - 1] = '\0';
+  s3_key_original_copy[sizeof(s3_key_original_copy) - 1] = '\0';
+  s3_key_processed_copy[sizeof(s3_key_processed_copy) - 1] = '\0';
+
+  Deita_Result_Set_Free(p_result);
+
+  // Update status to 'uploaded'
+  const char *update_query =
+    "UPDATE media_uploads SET status='uploaded', updated_at=strftime('%s','now') WHERE id=?";
+  const char *update_params[] = { media_id_str };
+  int32 result = Deita_Query_Execute_Update_Prepared(g_db_connection, update_query, 1, update_params);
+
+  if (result < 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Failed to update status\"}", arena);
+    return resp;
+  }
+
+  // If content_type starts with "image/", spawn background processing thread
+  if (strncmp(content_type_copy, "image/", 6) == 0)
+  {
+    // Create context for background thread (heap allocated)
+    Media_Processing_Context *ctx = malloc(sizeof(Media_Processing_Context));
+    ctx->media_id = media_id;
+    strncpy(ctx->s3_key_original, s3_key_original_copy, sizeof(ctx->s3_key_original) - 1);
+    strncpy(ctx->s3_key_processed, s3_key_processed_copy, sizeof(ctx->s3_key_processed) - 1);
+    strncpy(ctx->content_type, content_type_copy, sizeof(ctx->content_type) - 1);
+    strncpy(ctx->access_token, token, sizeof(ctx->access_token) - 1);
+    ctx->s3_key_original[sizeof(ctx->s3_key_original) - 1] = '\0';
+    ctx->s3_key_processed[sizeof(ctx->s3_key_processed) - 1] = '\0';
+    ctx->content_type[sizeof(ctx->content_type) - 1] = '\0';
+    ctx->access_token[sizeof(ctx->access_token) - 1] = '\0';
+    ctx->s3_config = g_s3_config;
+
+    // Spawn detached thread
+    pthread_t thread_id;
+    int thread_result = pthread_create(&thread_id, NULL, Media_Process_Background, ctx);
+
+    if (thread_result != 0)
+    {
+      Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: Failed to spawn processing thread for media_id=%lld\n", (long long)media_id);
+      free(ctx);
+    }
+    else
+    {
+      // Detach thread so it cleans up automatically when done
+      pthread_detach(thread_id);
+      Seobeo_Log(SEOBEO_INFO, "[MEDIA] Spawned processing thread for media_id=%lld\n", (long long)media_id);
+    }
+  }
+
+  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", "{\"success\":true,\"status\":\"uploaded\"}", arena);
+
+  Seobeo_Log(SEOBEO_INFO, "[MEDIA] Marked uploaded media_id=%lld\n", (long long)media_id);
+
+  return resp;
+}
+
+// Media Upload API - Get status
+// GET /api/media/:id/status
+// Headers: Authorization: Bearer <token>
+// Returns: {"id": 123, "status": "finished", "processed_url": "https://...", "error_message": null}
+Seobeo_Request_Entry *MediaStatus(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+
+  // Check auth token
+  void *auth_kv = Dowa_HashMap_Get_Ptr(req, "Authorization");
+  if (!auth_kv)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing Authorization header\"}", arena);
+    return resp;
+  }
+
+  const char *auth_header = ((Seobeo_Request_Entry*)auth_kv)->value;
+  if (strncmp(auth_header, "Bearer ", 7) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid Authorization format\"}", arena);
+    return resp;
+  }
+
+  const char *token = auth_header + 7;
+  if (strlen(g_upload_auth_token) == 0 || strcmp(token, g_upload_auth_token) != 0)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "403", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid token\"}", arena);
+    return resp;
+  }
+
+  if (!g_db_connection)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Database not available\"}", arena);
+    return resp;
+  }
+
+  // Extract media_id from URL params
+  void *id_kv = Dowa_HashMap_Get_Ptr(req, ":id");
+  if (!id_kv)
+  {
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing media ID\"}", arena);
+    return resp;
+  }
+
+  const char *media_id_str = ((Seobeo_Request_Entry*)id_kv)->value;
+
+  // Query media status
+  const char *select_query =
+    "SELECT id, status, s3_key_processed, error_message FROM media_uploads WHERE id = ? AND access_token = ?";
+  const char *select_params[] = { media_id_str, token };
+
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(g_db_connection, select_query, 2, select_params, arena);
+
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result) Deita_Result_Set_Free(p_result);
+    Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Media not found\"}", arena);
+    return resp;
+  }
+
+  int64 id = Deita_Result_Set_Get_Integer(p_result, 0);
+  const char *status = Deita_Result_Set_Get_Text(p_result, 1);
+  const char *s3_key_processed = Deita_Result_Set_Get_Text(p_result, 2);
+  const char *error_message = Deita_Result_Set_Get_Text(p_result, 3);
+
+  // Build CloudFront URL if status is 'finished' and s3_key_processed exists
+  char processed_url[1024] = {0};
+  if (strcmp(status, "finished") == 0 && s3_key_processed && strlen(s3_key_processed) > 0)
+  {
+    if (g_s3_cloudfront_url[0])
+    {
+      snprintf(processed_url, sizeof(processed_url), "%s/%s", g_s3_cloudfront_url, s3_key_processed);
+    }
+    else
+    {
+      snprintf(processed_url, sizeof(processed_url), "https://%s.s3.%s.amazonaws.com/%s",
+               g_s3_bucket, g_s3_region, s3_key_processed);
+    }
+  }
+
+  // Build JSON response
+  char *response_body = Dowa_Arena_Allocate(arena, 2048);
+  if (strlen(processed_url) > 0)
+  {
+    snprintf(response_body, 2048,
+             "{\"id\":%lld,\"status\":\"%s\",\"processed_url\":\"%s\",\"error_message\":%s}",
+             (long long)id, status, processed_url,
+             error_message ? "\"" : "null");
+    if (error_message)
+    {
+      // Append error message if exists
+      size_t len = strlen(response_body);
+      snprintf(response_body + len - 1, 2048 - len + 1, "%s\"}", error_message);
+    }
+  }
+  else
+  {
+    snprintf(response_body, 2048,
+             "{\"id\":%lld,\"status\":\"%s\",\"processed_url\":null,\"error_message\":%s}",
+             (long long)id, status,
+             error_message ? "\"" : "null");
+    if (error_message)
+    {
+      size_t len = strlen(response_body);
+      snprintf(response_body + len - 1, 2048 - len + 1, "%s\"}", error_message);
+    }
+  }
+
+  Deita_Result_Set_Free(p_result);
+
+  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", response_body, arena);
+
+  return resp;
+}
+
 int main(void)
 {
   // Load server config
@@ -742,6 +1715,9 @@
   printf("[S3] Configured: region=%s, bucket=%s, key=%s...\n",
          g_s3_region, g_s3_bucket, s3_access_key[0] ? "***" : "(missing)");
 
+  // Initialize database
+  init_database();
+
   Seobeo_Router_Init();
 
   Seobeo_Router_Register("GET", "/", GetHomePage);
@@ -767,6 +1743,15 @@
   // -- S3 Upload --/
   Seobeo_Router_Register("POST", "/api/s3/upload-url", GetS3UploadUrl);
 
+  // -- Media Upload --/
+  Seobeo_Router_Register("POST", "/api/media/create", MediaCreate);
+  Seobeo_Router_Register("POST", "/api/media/:id/uploaded", MediaUploaded);
+  Seobeo_Router_Register("GET", "/api/media/:id/status", MediaStatus);
+
+  // -- Editor --/
+  Seobeo_Router_Register("POST", "/api/editor/save", EditorSave);
+  Seobeo_Router_Register("GET", "/api/editor/load/:doc_id", EditorLoad);
+
   // -- Blog --/
   Seobeo_Router_Register("GET", "/blog", RenderBlogList);
   Seobeo_Router_Register("GET", "/blog/:blog_id", RenderBlog);
@@ -775,8 +1760,21 @@
   Seobeo_Router_Register("GET", "/talk", GetTalk);
   Seobeo_Router_Register("GET", "/talk/index.html", GetRedirectTalk);
 
+  // -- Editor (legacy) --/
+  Seobeo_Router_Register("GET", "/editor", GetEditor);
+  Seobeo_Router_Register("GET", "/editor/index.html", GetRedirectEditor);
+
+  // -- Notes --/
+  Seobeo_Router_Register("GET", "/notes", GetNotes);
+  Seobeo_Router_Register("GET", "/notes/", GetNotes);
+  Seobeo_Router_Register("GET", "/notes/index.html", GetNotes);
+  Seobeo_Router_Register("GET", "/notes/login", GetNotesLogin);
+  Seobeo_Router_Register("GET", "/notes/login/", GetNotesLogin);
+  Seobeo_Router_Register("GET", "/notes/:note_id", GetNoteById);
+
   Seobeo_WebSocket_Server_Init();
   Seobeo_WebSocket_Server_Register("/chat", Chat_Handler, NULL);
 
-  Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 3);
+  Seobeo_Log(SEOBEO_INFO, "WTF is going on\n");
+  Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 1);
 }