comparison 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
comparison
equal deleted inserted replaced
199:b4a070994b54 200:90dfcef375fb
1 #include "seobeo/seobeo.h" 1 #include "seobeo/seobeo.h"
2 #include "markdown_converter/markdown_to_html.h" 2 #include "markdown_converter/markdown_to_html.h"
3 #include "s3/s3_uploader.h"
3 #include <time.h> 4 #include <time.h>
4 5
5 // UUID + /tmp/ + format (max 4) 6 // UUID + /tmp/ + format (max 4)
6 #define TMP_FILE_LENGTH 47 7 #define TMP_FILE_LENGTH 47
7 #define UUID_LEN 37 8 #define UUID_LEN 37
8 9
9 volatile sig_atomic_t stop_server = 0; 10 volatile sig_atomic_t stop_server = 0;
10 static _Atomic uint32_t counter = 0; 11 static _Atomic uint32_t counter = 0;
12
13 // Server configuration (loaded from .config)
14 static char g_upload_auth_token[256] = {0};
15 static char g_s3_region[64] = "us-west-2";
16 static char g_s3_bucket[128] = "mrjunejune";
17 static int g_s3_url_expires = 3600;
18 static S3_Config g_s3_config = {0};
19
20 static void load_config(const char *config_path)
21 {
22 FILE *f = fopen(config_path, "r");
23 if (!f)
24 {
25 printf("[CONFIG] Warning: Could not open %s, using defaults\n", config_path);
26 return;
27 }
28
29 char line[512];
30 while (fgets(line, sizeof(line), f))
31 {
32 // Skip comments and empty lines
33 if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue;
34
35 char *eq = strchr(line, '=');
36 if (!eq) continue;
37
38 *eq = '\0';
39 char *key = line;
40 char *value = eq + 1;
41
42 // Trim newline from value
43 size_t vlen = strlen(value);
44 while (vlen > 0 && (value[vlen-1] == '\n' || value[vlen-1] == '\r'))
45 value[--vlen] = '\0';
46
47 if (strcmp(key, "UPLOAD_AUTH_TOKEN") == 0)
48 {
49 strncpy(g_upload_auth_token, value, sizeof(g_upload_auth_token) - 1);
50 }
51 else if (strcmp(key, "S3_REGION") == 0)
52 {
53 strncpy(g_s3_region, value, sizeof(g_s3_region) - 1);
54 }
55 else if (strcmp(key, "S3_BUCKET") == 0)
56 {
57 strncpy(g_s3_bucket, value, sizeof(g_s3_bucket) - 1);
58 }
59 else if (strcmp(key, "S3_URL_EXPIRES") == 0)
60 {
61 g_s3_url_expires = atoi(value);
62 }
63 }
64 fclose(f);
65
66 printf("[CONFIG] Loaded: token=%s..., region=%s, bucket=%s, expires=%d\n",
67 g_upload_auth_token[0] ? "***" : "(empty)",
68 g_s3_region, g_s3_bucket, g_s3_url_expires);
69 }
11 70
12 void handle_sigint(int sig) 71 void handle_sigint(int sig)
13 { 72 {
14 printf("Failed\n"); 73 printf("Failed\n");
15 stop_server = 1; 74 stop_server = 1;
501 CREATE_REDIRECT_HANDLER(Tools, "/tools") 560 CREATE_REDIRECT_HANDLER(Tools, "/tools")
502 CREATE_REDIRECT_HANDLER(MarkDownToHtml, "/tools/markdown_to_html") 561 CREATE_REDIRECT_HANDLER(MarkDownToHtml, "/tools/markdown_to_html")
503 CREATE_REDIRECT_HANDLER(FileConverter, "/tools/file_converter") 562 CREATE_REDIRECT_HANDLER(FileConverter, "/tools/file_converter")
504 CREATE_REDIRECT_HANDLER(Talk, "/talk") 563 CREATE_REDIRECT_HANDLER(Talk, "/talk")
505 564
565 // S3 Upload URL API
566 // POST /api/s3/upload-url
567 // Headers: Authorization: Bearer <token>, Content-Type: application/json
568 // Body: {"filename": "photo.png", "content_type": "image/png"}
569 // Returns: {"upload_url": "https://...", "key": "uploads/..."}
570 Seobeo_Request_Entry *GetS3UploadUrl(Seobeo_Request_Entry *req, Dowa_Arena *arena)
571 {
572 Seobeo_Request_Entry *resp = NULL;
573
574 // Check auth token
575 void *auth_kv = Dowa_HashMap_Get_Ptr(req, "Authorization");
576 if (!auth_kv)
577 {
578 Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
579 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
580 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing Authorization header\"}", arena);
581 return resp;
582 }
583
584 const char *auth_header = ((Seobeo_Request_Entry*)auth_kv)->value;
585
586 // Expect "Bearer <token>"
587 if (strncmp(auth_header, "Bearer ", 7) != 0)
588 {
589 Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
590 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
591 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid Authorization format, use Bearer token\"}", arena);
592 return resp;
593 }
594
595 const char *token = auth_header + 7;
596 if (strlen(g_upload_auth_token) == 0 || strcmp(token, g_upload_auth_token) != 0)
597 {
598 Dowa_HashMap_Push_Arena(resp, "status", "403", arena);
599 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
600 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid token\"}", arena);
601 return resp;
602 }
603
604 // Parse request body for filename and content_type
605 void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
606 if (!body_kv)
607 {
608 Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
609 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
610 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing request body\"}", arena);
611 return resp;
612 }
613
614 const char *body = ((Seobeo_Request_Entry*)body_kv)->value;
615
616 // Simple JSON parsing for filename and content_type
617 char filename[256] = {0};
618 char content_type[128] = "application/octet-stream";
619
620 // Find "filename":"value"
621 const char *fn_key = strstr(body, "\"filename\"");
622 if (fn_key)
623 {
624 const char *fn_start = strchr(fn_key + 10, '"');
625 if (fn_start)
626 {
627 fn_start++;
628 const char *fn_end = strchr(fn_start, '"');
629 if (fn_end && (size_t)(fn_end - fn_start) < sizeof(filename))
630 {
631 memcpy(filename, fn_start, fn_end - fn_start);
632 filename[fn_end - fn_start] = '\0';
633 }
634 }
635 }
636
637 // Find "content_type":"value"
638 const char *ct_key = strstr(body, "\"content_type\"");
639 if (ct_key)
640 {
641 const char *ct_start = strchr(ct_key + 14, '"');
642 if (ct_start)
643 {
644 ct_start++;
645 const char *ct_end = strchr(ct_start, '"');
646 if (ct_end && (size_t)(ct_end - ct_start) < sizeof(content_type))
647 {
648 memcpy(content_type, ct_start, ct_end - ct_start);
649 content_type[ct_end - ct_start] = '\0';
650 }
651 }
652 }
653
654 if (strlen(filename) == 0)
655 {
656 Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
657 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
658 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing filename in request body\"}", arena);
659 return resp;
660 }
661
662 // Generate unique S3 key with timestamp
663 char s3_key[512];
664 char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN);
665 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
666 Dowa_String_UUID(seed, uuid);
667 snprintf(s3_key, sizeof(s3_key), "uploads/%s/%s", uuid, filename);
668
669 // Generate presigned URL
670 S3_Presigned_URL presigned = S3_Presign_Put(&g_s3_config, s3_key, content_type, g_s3_url_expires);
671
672 if (!presigned.success)
673 {
674 Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
675 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
676 char *error_body = Dowa_Arena_Allocate(arena, 256);
677 snprintf(error_body, 256, "{\"error\":\"Failed to generate upload URL: %s\"}",
678 presigned.error_message ? presigned.error_message : "unknown");
679 Dowa_HashMap_Push_Arena(resp, "body", error_body, arena);
680 S3_Presigned_URL_Destroy(&presigned);
681 return resp;
682 }
683
684 // Build response
685 char *response_body = Dowa_Arena_Allocate(arena, 2048 + strlen(presigned.url));
686 snprintf(response_body, 2048 + strlen(presigned.url),
687 "{\"upload_url\":\"%s\",\"key\":\"%s\",\"expires\":%d}",
688 presigned.url, s3_key, g_s3_url_expires);
689
690 S3_Presigned_URL_Destroy(&presigned);
691
692 Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
693 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
694 Dowa_HashMap_Push_Arena(resp, "body", response_body, arena);
695
696 printf("[S3] Generated upload URL for: %s\n", s3_key);
697
698 return resp;
699 }
700
506 int main(void) 701 int main(void)
507 { 702 {
703 // Load server config
704 load_config("mrjunejune/.config");
705
706 // Load S3 credentials from .env
707 FILE *env_file = fopen(".env", "r");
708 static char s3_access_key[128] = {0};
709 static char s3_secret_key[128] = {0};
710
711 if (env_file)
712 {
713 char line[512];
714 while (fgets(line, sizeof(line), env_file))
715 {
716 if (strncmp(line, "AWS_MRJUNEJUNE_ACCESS_KEY=", 26) == 0)
717 {
718 char *val = line + 26;
719 size_t len = strlen(val);
720 while (len > 0 && (val[len-1] == '\n' || val[len-1] == '\r')) val[--len] = '\0';
721 strncpy(s3_access_key, val, sizeof(s3_access_key) - 1);
722 }
723 else if (strncmp(line, "AWS_MRJUNEJUNE_SECRET_ACCESS_KEY=", 33) == 0)
724 {
725 char *val = line + 33;
726 size_t len = strlen(val);
727 while (len > 0 && (val[len-1] == '\n' || val[len-1] == '\r')) val[--len] = '\0';
728 strncpy(s3_secret_key, val, sizeof(s3_secret_key) - 1);
729 }
730 }
731 fclose(env_file);
732 }
733
734 // Initialize S3 config
735 g_s3_config.access_key_id = s3_access_key;
736 g_s3_config.secret_access_key = s3_secret_key;
737 g_s3_config.region = g_s3_region;
738 g_s3_config.bucket = g_s3_bucket;
739 g_s3_config.endpoint = NULL;
740 g_s3_config.use_path_style = FALSE;
741
742 printf("[S3] Configured: region=%s, bucket=%s, key=%s...\n",
743 g_s3_region, g_s3_bucket, s3_access_key[0] ? "***" : "(missing)");
744
508 Seobeo_Router_Init(); 745 Seobeo_Router_Init();
509 746
510 Seobeo_Router_Register("GET", "/", GetHomePage); 747 Seobeo_Router_Register("GET", "/", GetHomePage);
511 Seobeo_Router_Register("GET", "/index.html", GetRedirectHomePage); 748 Seobeo_Router_Register("GET", "/index.html", GetRedirectHomePage);
512 749
525 // -- File converter --/ 762 // -- File converter --/
526 Seobeo_Router_Register("POST", "/api/convert/image-to-webp", ConvertImageToWebP); 763 Seobeo_Router_Register("POST", "/api/convert/image-to-webp", ConvertImageToWebP);
527 Seobeo_Router_Register("POST", "/api/convert/video-to-mp4", ConvertVideoToMP4); 764 Seobeo_Router_Register("POST", "/api/convert/video-to-mp4", ConvertVideoToMP4);
528 Seobeo_Router_Register("GET", "/api/download/:filename", DownloadConvertedFile); 765 Seobeo_Router_Register("GET", "/api/download/:filename", DownloadConvertedFile);
529 766
767 // -- S3 Upload --/
768 Seobeo_Router_Register("POST", "/api/s3/upload-url", GetS3UploadUrl);
769
530 // -- Blog --/ 770 // -- Blog --/
531 Seobeo_Router_Register("GET", "/blog", RenderBlogList); 771 Seobeo_Router_Register("GET", "/blog", RenderBlogList);
532 Seobeo_Router_Register("GET", "/blog/:blog_id", RenderBlog); 772 Seobeo_Router_Register("GET", "/blog/:blog_id", RenderBlog);
533 773
534 // -- Talk --/ 774 // -- Talk --/