Mercurial
view mrjunejune/main.c @ 200:90dfcef375fb
Added my own s3 bucket uploader url to mrjunejune.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sat, 14 Feb 2026 16:32:24 -0800 |
| parents | 295ac2e5ec00 |
| children | 6cdee35a7ba9 |
line wrap: on
line source
#include "seobeo/seobeo.h" #include "markdown_converter/markdown_to_html.h" #include "s3/s3_uploader.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; // 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 int g_s3_url_expires = 3600; static S3_Config g_s3_config = {0}; static void load_config(const char *config_path) { FILE *f = fopen(config_path, "r"); if (!f) { printf("[CONFIG] Warning: Could not open %s, using defaults\n", config_path); return; } char line[512]; while (fgets(line, sizeof(line), f)) { // Skip comments and empty lines if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue; char *eq = strchr(line, '='); if (!eq) continue; *eq = '\0'; char *key = line; char *value = eq + 1; // Trim newline from value size_t vlen = strlen(value); while (vlen > 0 && (value[vlen-1] == '\n' || value[vlen-1] == '\r')) value[--vlen] = '\0'; if (strcmp(key, "UPLOAD_AUTH_TOKEN") == 0) { strncpy(g_upload_auth_token, value, sizeof(g_upload_auth_token) - 1); } else if (strcmp(key, "S3_REGION") == 0) { strncpy(g_s3_region, value, sizeof(g_s3_region) - 1); } else if (strcmp(key, "S3_BUCKET") == 0) { strncpy(g_s3_bucket, value, sizeof(g_s3_bucket) - 1); } else if (strcmp(key, "S3_URL_EXPIRES") == 0) { g_s3_url_expires = atoi(value); } } fclose(f); printf("[CONFIG] Loaded: token=%s..., region=%s, bucket=%s, expires=%d\n", g_upload_auth_token[0] ? "***" : "(empty)", g_s3_region, g_s3_bucket, g_s3_url_expires); } void handle_sigint(int sig) { printf("Failed\n"); stop_server = 1; } void Seobeo_Render_Html( char *final_body, char *template, Dowa_Arena *arena ) { size_t current_offset = 0; char *cursor = template; int32 token_len = 2; while (1) { char *start_tag = strstr(cursor, "{{"); if (!start_tag) break; char *end_tag = strstr(start_tag, "}}"); if (!end_tag) break; size_t leading_len = start_tag - cursor; memcpy(final_body + current_offset, cursor, leading_len); current_offset += leading_len; size_t name_len = end_tag - (start_tag + token_len); char *include_name = Dowa_Arena_Allocate(arena, name_len + 1); memcpy(include_name, start_tag + token_len, name_len); include_name[name_len] = '\0'; 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); if (sub_content) { memcpy(final_body + current_offset, sub_content, sub_file_size); current_offset += sub_file_size; free(sub_content); } cursor = end_tag + 2; } 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); size_t html_size = 0; char *template = Seobeo_Web_LoadFile(path, &html_size); if (!template) return; Seobeo_Render_Html(final_body, template, arena); } Seobeo_Request_Entry* GetHomePage(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, "/index.html", arena); Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); return resp; } Seobeo_Request_Entry* GetResume(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, "/resume/index.html", arena); Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); return resp; } Seobeo_Request_Entry* GetTools(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, "/tools/index.html", arena); Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); return resp; } Seobeo_Request_Entry* GetMDToHTML(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, "/tools/markdown_to_html/index.html", arena); Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); return resp; } 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_Render_Html_FilePath(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) { 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); 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); Seobeo_Log(SEOBEO_DEBUG, "Input path: %s\n", input_path); int input_fd = open(input_path, open_flags, 0600); if (input_fd == -1) { Seobeo_Log(SEOBEO_DEBUG, "errno: %d (%s)\n", errno, strerror(errno)); 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); 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; 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); 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; } Seobeo_Request_Entry *RenderBlogList(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, "/blog/index.html", arena); Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); return resp; } Seobeo_Request_Entry *RenderBlog(Seobeo_Request_Entry *req, Dowa_Arena *arena) { Seobeo_Request_Entry *resp = NULL; char *file_path = Dowa_Arena_Allocate(arena, 1024); void *blog_id_kv = Dowa_HashMap_Get_Ptr(req, ":blog_id"); char *blog_id = ((Seobeo_Request_Entry*)blog_id_kv)->value; snprintf(file_path, 1024, "/blog/%s/index.html", blog_id); char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); Seobeo_Render_Html_FilePath(final_body, file_path, arena); Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); 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_Render_Html_FilePath(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") // S3 Upload URL API // POST /api/s3/upload-url // Headers: Authorization: Bearer <token>, Content-Type: application/json // Body: {"filename": "photo.png", "content_type": "image/png"} // Returns: {"upload_url": "https://...", "key": "uploads/..."} Seobeo_Request_Entry *GetS3UploadUrl(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; // Expect "Bearer <token>" 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, use Bearer token\"}", 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; } // Parse request body for filename and content_type 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; // Simple JSON parsing for 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 in request body\"}", arena); return resp; } // Generate unique S3 key with timestamp char s3_key[512]; char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN); uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; Dowa_String_UUID(seed, uuid); snprintf(s3_key, sizeof(s3_key), "uploads/%s/%s", uuid, filename); // Generate presigned URL S3_Presigned_URL presigned = S3_Presign_Put(&g_s3_config, s3_key, 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, 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); 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("[S3] Generated upload URL for: %s\n", s3_key); return resp; } int main(void) { // Load server config load_config("mrjunejune/.config"); // Load S3 credentials from .env FILE *env_file = fopen(".env", "r"); static char s3_access_key[128] = {0}; static char s3_secret_key[128] = {0}; if (env_file) { char line[512]; while (fgets(line, sizeof(line), env_file)) { if (strncmp(line, "AWS_MRJUNEJUNE_ACCESS_KEY=", 26) == 0) { char *val = line + 26; size_t len = strlen(val); while (len > 0 && (val[len-1] == '\n' || val[len-1] == '\r')) val[--len] = '\0'; strncpy(s3_access_key, val, sizeof(s3_access_key) - 1); } else if (strncmp(line, "AWS_MRJUNEJUNE_SECRET_ACCESS_KEY=", 33) == 0) { char *val = line + 33; size_t len = strlen(val); while (len > 0 && (val[len-1] == '\n' || val[len-1] == '\r')) val[--len] = '\0'; strncpy(s3_secret_key, val, sizeof(s3_secret_key) - 1); } } fclose(env_file); } // Initialize S3 config g_s3_config.access_key_id = s3_access_key; g_s3_config.secret_access_key = s3_secret_key; g_s3_config.region = g_s3_region; g_s3_config.bucket = g_s3_bucket; g_s3_config.endpoint = NULL; g_s3_config.use_path_style = FALSE; printf("[S3] Configured: region=%s, bucket=%s, key=%s...\n", g_s3_region, g_s3_bucket, s3_access_key[0] ? "***" : "(missing)"); Seobeo_Router_Init(); Seobeo_Router_Register("GET", "/", GetHomePage); Seobeo_Router_Register("GET", "/index.html", GetRedirectHomePage); Seobeo_Router_Register("GET", "/resume", GetResume); Seobeo_Router_Register("GET", "/resume/index.html", GetRedirectResume); Seobeo_Router_Register("GET", "/tools", GetTools); Seobeo_Router_Register("GET", "/tools/index.html", GetRedirectTools); 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); // -- File converter --/ 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); // -- S3 Upload --/ Seobeo_Router_Register("POST", "/api/s3/upload-url", GetS3UploadUrl); // -- Blog --/ Seobeo_Router_Register("GET", "/blog", RenderBlogList); Seobeo_Router_Register("GET", "/blog/:blog_id", RenderBlog); // -- Talk --/ Seobeo_Router_Register("GET", "/talk", GetTalk); Seobeo_Router_Register("GET", "/talk/index.html", GetRedirectTalk); Seobeo_WebSocket_Server_Init(); Seobeo_WebSocket_Server_Register("/chat", Chat_Handler, NULL); Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 3); }