Mercurial
view mrjunejune/main.c @ 203:92a57bd716c1
removed unused file
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 15 Feb 2026 09:13:09 -0800 |
| parents | b9b184b3303c |
| children | e5aed6c36672 |
line wrap: on
line source
#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 #define UUID_LEN 37 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]; char db_path[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) { 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); } 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, cloudfront=%s, db=%s\n", g_upload_auth_token[0] ? "***" : "(empty)", 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) { 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; 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; 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, "[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); 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, "[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); } 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) { 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); 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++) { Seobeo_Log(SEOBEO_INFO, " 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; } 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 // 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 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, 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); 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; } // 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; Seobeo_Log(SEOBEO_INFO, "[MEDIA] Background thread started for media_id=%lld\n", (long long)ctx->media_id); Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key original: %s\n", ctx->s3_key_original); Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key processed: %s\n", ctx->s3_key_processed); Seobeo_Log(SEOBEO_INFO, "[MEDIA] DB path: %s\n", ctx->db_path); // Open thread-local DB connection Deita_Connection *db_conn = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, ctx->db_path); if (!db_conn || !Deita_Connection_Is_Open(db_conn)) { Seobeo_Log(SEOBEO_ERROR, "[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 }; int32 update_result = Deita_Query_Execute_Update_Prepared(db_conn, update_processing, 1, params); Seobeo_Log(SEOBEO_INFO, "[MEDIA] Updated status to 'processing' for media_id=%lld (result=%d)\n", (long long)ctx->media_id, update_result); // Generate presigned GET URL for download (10 min expiry) Seobeo_Log(SEOBEO_INFO, "[MEDIA] Generating presigned GET URL for media_id=%lld\n", (long long)ctx->media_id); S3_Presigned_URL download_url = S3_Presign_Get(&ctx->s3_config, ctx->s3_key_original, 600); if (!download_url.success) { const char *error_msg = download_url.error_message ? download_url.error_message : "Failed to generate download URL"; Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: Failed to generate download URL for media_id=%lld: %s\n", (long long)ctx->media_id, error_msg); const char *update_error = "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; const char *error_params[] = { error_msg, media_id_str }; Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); S3_Presigned_URL_Destroy(&download_url); Deita_Connection_Close(db_conn); free(ctx); return NULL; } Seobeo_Log(SEOBEO_INFO, "[MEDIA] Generated presigned URL: %.100s...\n", download_url.url); // 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_Log(SEOBEO_INFO, "[MEDIA] Downloading from S3 to %s for media_id=%lld\n", tmp_input, (long long)ctx->media_id); 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) { int status = download_resp ? download_resp->status_code : 0; Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: Failed to download from S3 for media_id=%lld (status=%d)\n", (long long)ctx->media_id, status); 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); 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_Log(SEOBEO_INFO, "[MEDIA] Successfully downloaded file to %s\n", tmp_input); 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); Seobeo_Log(SEOBEO_INFO, "[MEDIA] Running FFmpeg: %s\n", cmd); int ffmpeg_result = system(cmd); Seobeo_Log(SEOBEO_INFO, "[MEDIA] FFmpeg result: %d for media_id=%lld\n", ffmpeg_result, (long long)ctx->media_id); if (ffmpeg_result != 0) { Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: FFmpeg conversion failed for media_id=%lld (exit code %d). Check log: %s\n", (long long)ctx->media_id, ffmpeg_result, log_file); 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); unlink(tmp_input); unlink(tmp_output); Deita_Connection_Close(db_conn); free(ctx); return NULL; } Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n", tmp_output); // Upload processed file to S3 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Uploading processed file to S3: %s -> %s\n", tmp_output, ctx->s3_key_processed); 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 *error_msg = upload_result.error_message ? upload_result.error_message : "Failed to upload processed file"; Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: Failed to upload processed file for media_id=%lld: %s\n", (long long)ctx->media_id, error_msg); const char *update_error = "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; const char *error_params[] = { error_msg, media_id_str }; Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); unlink(tmp_input); unlink(tmp_output); Deita_Connection_Close(db_conn); free(ctx); return NULL; } Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully uploaded processed file to S3\n"); // 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); Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully processed media_id=%lld - COMPLETE\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; } Seobeo_Log(SEOBEO_INFO, "[MEDIA] Content type for media_id=%lld: '%s'\n", (long long)media_id, content_type_copy); // If content_type starts with "image/", spawn background processing thread if (strncmp(content_type_copy, "image/", 6) == 0) { Seobeo_Log(SEOBEO_INFO, "[MEDIA] Detected image type, preparing to spawn background thread for media_id=%lld\n", (long long)media_id); // 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); strncpy(ctx->db_path, g_db_path, sizeof(ctx->db_path) - 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->db_path[sizeof(ctx->db_path) - 1] = '\0'; ctx->s3_config = g_s3_config; Seobeo_Log(SEOBEO_INFO, "[MEDIA] Creating pthread for media_id=%lld\n", (long long)media_id); // 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: pthread_create failed with result=%d for media_id=%lld\n", thread_result, (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] Successfully spawned and detached thread for media_id=%lld\n", (long long)media_id); } } else { Seobeo_Log(SEOBEO_INFO, "[MEDIA] Non-image file, skipping background processing 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_original, 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_original = Deita_Result_Set_Get_Text(p_result, 2); const char *s3_key_processed = Deita_Result_Set_Get_Text(p_result, 3); const char *error_message = Deita_Result_Set_Get_Text(p_result, 4); // Build CloudFront URL for processed file if status is 'finished' 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 CloudFront URL for original file (for non-images or before processing completes) char original_url[1024] = {0}; if (s3_key_original && strlen(s3_key_original) > 0) { if (g_s3_cloudfront_url[0]) { snprintf(original_url, sizeof(original_url), "%s/%s", g_s3_cloudfront_url, s3_key_original); } else { snprintf(original_url, sizeof(original_url), "https://%s.s3.%s.amazonaws.com/%s", g_s3_bucket, g_s3_region, s3_key_original); } } // Build JSON response with both processed_url and original_url char *response_body = Dowa_Arena_Allocate(arena, 3072); // Build the base response int offset = snprintf(response_body, 3072, "{\"id\":%lld,\"status\":\"%s\",", (long long)id, status); // Add processed_url if (strlen(processed_url) > 0) { offset += snprintf(response_body + offset, 3072 - offset, "\"processed_url\":\"%s\",", processed_url); } else { offset += snprintf(response_body + offset, 3072 - offset, "\"processed_url\":null,"); } // Add original_url if (strlen(original_url) > 0) { offset += snprintf(response_body + offset, 3072 - offset, "\"original_url\":\"%s\",", original_url); } else { offset += snprintf(response_body + offset, 3072 - offset, "\"original_url\":null,"); } // Add error_message if (error_message && strlen(error_message) > 0) { snprintf(response_body + offset, 3072 - offset, "\"error_message\":\"%s\"}", error_message); } else { snprintf(response_body + offset, 3072 - offset, "\"error_message\":null}"); } 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 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)"); // Show current working directory char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) != NULL) { printf("[STARTUP] Current working directory: %s\n", cwd); printf("[STARTUP] Database path (relative): %s\n", g_db_path); } // Initialize database init_database(); 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); // -- 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); // -- Talk --/ Seobeo_Router_Register("GET", "/talk", GetTalk); Seobeo_Router_Register("GET", "/talk/index.html", GetRedirectTalk); // -- 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_Log(SEOBEO_INFO, "WTF is going on\n"); Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 1); }