diff mrjunejune/main.c @ 92:655ea0b661fd

[Seobeo] Added few endpoints for handling files. [Dowa] Added few functions for random number and generating uuids
author June Park <parkjune1995@gmail.com>
date Fri, 02 Jan 2026 17:47:10 -0800
parents b5e65b01f0a3
children 70401cf61e97
line wrap: on
line diff
--- a/mrjunejune/main.c	Thu Jan 01 16:34:51 2026 -0800
+++ b/mrjunejune/main.c	Fri Jan 02 17:47:10 2026 -0800
@@ -1,6 +1,12 @@
 #include "seobeo/seobeo.h"
+#include <time.h>
+
+// UUID + /tmp/ + format (max 4)
+#define TMP_FILE_LENGTH 47
+#define UUID_LEN 37
 
 volatile sig_atomic_t stop_server = 0;
+static _Atomic uint32_t counter = 0;
 
 void handle_sigint(int sig)
 {
@@ -84,23 +90,348 @@
 
 Seobeo_Request_Entry* GetMDToHTML(Seobeo_Request_Entry *req, Dowa_Arena *arena)
 {
-  Seobeo_Request_Entry *resp = NULL; 
+  Seobeo_Request_Entry *resp = NULL;
   char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
   Seobeo_ServerSideRender(final_body, "/tools/markdown_to_html/index.html", arena);
   Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
   return resp;
 }
 
-Seobeo_Request_Entry* GetUser(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+Seobeo_Request_Entry* GetFileConverter(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, "/tools/file_converter/index.html", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
+  return resp;
+}
+
+Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena)
 {
-  void *id_kv = Dowa_HashMap_Get_Ptr(req, ":id");
-  const char *user_id = id_kv ? ((Seobeo_Request_Entry*)id_kv)->value : "unknown";
+  Seobeo_Request_Entry *resp = NULL;
+
+  if (!req)
+  {
+    printf("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);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  size_t req_length = Dowa_Array_Length(req);
+  printf("Request has %zu entries\n", req_length);
+
+  for (size_t i = 0; i < req_length; i++)
+  {
+    printf("  Key[%zu]: '%s'\n", i, req[i].key);
+  }
+
+  void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
+  if (!body_kv)
+  {
+    printf("ERROR: No 'Body' key found in request\n");
+
+    char *error_msg = "No file data provided";
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length");
+  if (!cl_kv)
+  {
+    char *error_msg = "No Content-Length header";
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value;
+  const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value;
+  size_t file_size = atoi(content_length_str);
+
+  printf("DEBUG: Converting image, file_size=%zu bytes\n", file_size);
+
+   
+  int open_flags = O_RDWR | O_CREAT | O_EXCL;
+
+  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
+  uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
+  Dowa_String_UUID(seed, uuid4);
+  char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
+  snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4);
+  int input_fd = open(input_path, open_flags, 0600);
+  if (input_fd == -1)
+  {
+    char *error_msg = "Failed to create temporary file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+  write(input_fd, file_data, file_size);
+  close(input_fd);
+
+
+  uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
+  seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
+  Dowa_String_UUID(seed, uuid4);
+  char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
+  snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4);
+  printf("[DEBUG] output_path %s\n", output_path);
+  printf("[DEBUG] open_flags: 0x%x\n", open_flags);
+  printf("[DEBUG] input_path: %s\n", input_path);
+  int output_fd = open(output_path, open_flags, 0600);
+  printf("[DEBUG] output_fd: %d\n", output_fd);
+  if (output_fd == -1)
+  {
+    unlink(input_path);
+    printf("[DEBUG] errno: %d (%s)\n", errno, strerror(errno));
+    char *error_msg = "Failed to create output file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+  close(output_fd);
+
+  char cmd[1024];
+  snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log",
+           input_path, output_path);
+  int result = system(cmd);
+  if (result != 0)
+  {
+    unlink(input_path);
+    unlink(output_path);
+    char *error_msg = "FFmpeg conversion failed";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  size_t converted_size = 0;
+  FILE *out_file = fopen(output_path, "rb");
+  if (!out_file)
+  {
+    unlink(input_path);
+    unlink(output_path);
+    char *error_msg = "Failed to read converted file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+  fclose(out_file);
+
+  unlink(input_path);
+
+  char *filename = strrchr(output_path, '/') + 1;
+  char *response_body = Dowa_Arena_Allocate(arena, 512);
+  snprintf(response_body, 512,
+           "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}",
+           filename);
+  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("DEBUG: Image converted, available at /api/download/%s\n", filename);
+
+  return resp;
+}
+
+Seobeo_Request_Entry *ConvertVideoToMP4(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+
+  void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
+  if (!body_kv)
+  {
+    char *error_msg = "No file data provided";
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
 
+  // Get Content-Length to know the actual binary size
+  void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length");
+  if (!cl_kv)
+  {
+    char *error_msg = "No Content-Length header";
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value;
+  const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value;
+  size_t file_size = atoi(content_length_str);
+
+  printf("DEBUG: Converting video, file_size=%zu bytes\n", file_size);
+
+  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
+  Dowa_String_UUID((uint32)time(NULL), uuid4);
+  char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);
+  snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4);
+  int input_fd = mkstemp(input_path);
+  if (input_fd == -1)
+  {
+    char *error_msg = "Failed to create temporary file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  write(input_fd, file_data, file_size);
+  close(input_fd);
+
+  int open_flags = O_RDWR | O_CREAT | O_EXCL;
+
+  uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;;
+  Dowa_String_UUID(seed, uuid4);
+  char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
+  snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4);
+  int output_fd = open(output_path, open_flags, 0600);
+  if (output_fd == -1)
+  {
+    unlink(input_path);
+    char *error_msg = "Failed to create output file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+  close(output_fd);
+
+  char cmd[512];
+  snprintf(cmd, sizeof(cmd),
+           "ffmpeg -y -i %s -c:v libx264 -preset fast -crf 23 -c:a aac %s 2>/tmp/error_log",
+           input_path, output_path);
+  int result = system(cmd);
+  if (result != 0)
+  {
+    unlink(input_path);
+    unlink(output_path);
+    char *error_msg = "FFmpeg conversion failed";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  unlink(input_path);
+  char *filename = strrchr(output_path, '/') + 1;
+  char *response_body = Dowa_Arena_Allocate(arena, 512);
+  snprintf(response_body, 512,
+           "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}",
+           filename);
+  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("DEBUG: Video converted, available at /api/download/%s\n", filename);
+  return resp;
+}
+
+Seobeo_Request_Entry *DownloadConvertedFile(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
   Seobeo_Request_Entry *resp = NULL;
-  char *body = Dowa_Arena_Allocate(arena, 256);
-  snprintf(body, 256, "{\"id\":\"%s\",\"name\":\"John Doe\"}", user_id);
+
+  void *filename_kv = Dowa_HashMap_Get_Ptr(req, ":filename");
+  if (!filename_kv)
+  {
+    char *error_msg = "No filename specified";
+    Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  const char *filename = ((Seobeo_Request_Entry*)filename_kv)->value;
+
+  // TODO: Maybe check if the uuid is allowed or not?
+  // if (strlen(filename) != TMP_FILE_LENGTH)
+  // {
+  //   char *error_msg = "Not Allowed Filename";
+  //   Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
+  //   Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+  //   Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+  //   return resp;
+  // }
+  // boolean allowed = FALSE;
+  // for (int i = 0; i < Dowa_Array_Length(g_uuid4_array); i++)
+  // {
+  //   if strcmp(g_uuid4_array, filename)
+  //   {
+  //     allowed = TRUE;
+  //     break;
+  //   }
+  //   g_uuid4_array++;
+  // }
+
+  char filepath[512];
+  snprintf(filepath, sizeof(filepath), "/tmp/%s", filename);
 
+  FILE *file = fopen(filepath, "rb");
+  if (!file)
+  {
+    char *error_msg = "File not found or expired";
+    Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  fseek(file, 0, SEEK_END);
+  size_t file_size = ftell(file);
+  fseek(file, 0, SEEK_SET);
+
+  char *file_data = malloc(file_size + 1);
+  if (!file_data)
+  {
+    fclose(file);
+    char *error_msg = "Memory allocation failed";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  fread(file_data, 1, file_size, file);
+  file_data[file_size] = '\0';
+  fclose(file);
+
+  const char *content_type = "application/octet-stream";
+  if (strstr(filename, ".webp"))
+    content_type = "image/webp";
+  else if (strstr(filename, ".mp4"))
+    content_type = "video/mp4";
+
+  char *body = Dowa_Arena_Allocate(arena, file_size + 1);
+  memcpy(body, file_data, file_size);
+  body[file_size] = '\0';
+  free(file_data);
+
+  unlink(filepath);
+
+  printf("DEBUG: Served and deleted file: %s (%zu bytes)\n", filename, file_size);
+
+  // Set proper Content-Length for binary data
+  char *content_length = Dowa_Arena_Allocate(arena, 32);
+  snprintf(content_length, 32, "%zu", file_size);
+
+  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+  Dowa_HashMap_Push_Arena(resp, "content-type", content_type, arena);
+  Dowa_HashMap_Push_Arena(resp, "content-length", content_length, arena);
   Dowa_HashMap_Push_Arena(resp, "body", body, arena);
+
   return resp;
 }
 
@@ -108,6 +439,7 @@
 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")
 
 int main(void)
 {
@@ -124,5 +456,12 @@
   Seobeo_Router_Register("GET", "/tools/markdown_to_html", GetMDToHTML);
   Seobeo_Router_Register("GET", "/tools/markdown_to_html/index.html", GetRedirectMarkDownToHtml);
 
+  Seobeo_Router_Register("GET", "/tools/file_converter", GetFileConverter);
+  Seobeo_Router_Register("GET", "/tools/file_converter/index.html", GetRedirectFileConverter);
+
+  Seobeo_Router_Register("POST", "/api/convert/image-to-webp", ConvertImageToWebP);
+  Seobeo_Router_Register("POST", "/api/convert/video-to-mp4", ConvertVideoToMP4);
+  Seobeo_Router_Register("GET", "/api/download/:filename", DownloadConvertedFile);
+
   Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 3);
 }