# HG changeset patch # User MrJuneJune # Date 1771184513 28800 # Node ID 240337164a80b75c0e2a072a6b8d29b5ef20cd8a # Parent e07b4b5a66bb2f5f656459d53c5809e6bc210ad9 [Seobeo] SSL should be used for large file as well lol. diff -r e07b4b5a66bb -r 240337164a80 mrjunejune/main.c --- a/mrjunejune/main.c Sun Feb 15 11:07:52 2026 -0800 +++ b/mrjunejune/main.c Sun Feb 15 11:41:53 2026 -0800 @@ -1402,8 +1402,8 @@ 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); + Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: Failed to upload processed file for media_id=%lld: %s (HTTP status: %d)\n", + (long long)ctx->media_id, error_msg, upload_result.status_code); 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 }; diff -r e07b4b5a66bb -r 240337164a80 rich_editor/rich_editor.js --- a/rich_editor/rich_editor.js Sun Feb 15 11:07:52 2026 -0800 +++ b/rich_editor/rich_editor.js Sun Feb 15 11:41:53 2026 -0800 @@ -160,6 +160,83 @@ height: auto; border-radius: 4px; margin: 8px 0; + cursor: pointer; + } + + .rich-editor-content img.selected { + outline: 2px solid #0078ff; + outline-offset: 2px; + } + + .rich-editor-resize-wrapper { + position: relative; + display: inline-block; + margin: 8px 0; + } + + .rich-editor-resize-handle { + position: absolute; + width: 12px; + height: 12px; + background: #0078ff; + border: 2px solid white; + border-radius: 50%; + cursor: nwse-resize; + z-index: 10; + } + + .rich-editor-resize-handle.nw { + top: -6px; + left: -6px; + cursor: nwse-resize; + } + + .rich-editor-resize-handle.ne { + top: -6px; + right: -6px; + cursor: nesw-resize; + } + + .rich-editor-resize-handle.sw { + bottom: -6px; + left: -6px; + cursor: nesw-resize; + } + + .rich-editor-resize-handle.se { + bottom: -6px; + right: -6px; + cursor: nwse-resize; + } + + .rich-editor-size-input { + position: absolute; + top: -35px; + left: 50%; + transform: translateX(-50%); + background: white; + border: 1px solid #ccc; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + display: flex; + gap: 8px; + align-items: center; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + z-index: 11; + } + + .rich-editor-size-input input { + width: 60px; + padding: 2px 4px; + border: 1px solid #ccc; + border-radius: 2px; + font-size: 12px; + } + + .rich-editor-size-input label { + font-size: 11px; + color: #666; } .rich-editor-content a { @@ -242,7 +319,11 @@ constructor(elementId, options) { this.options = { ...DEFAULT_OPTIONS, ...options }; this.container = document.getElementById(elementId); - this.state = { readOnly: false }; + this.state = { + readOnly: false, + selectedImage: null, + resizing: false + }; if (!this.container) { throw new Error(`Element with id "${elementId}" not found`); @@ -391,6 +472,23 @@ } } }); + + // Handle image clicks for resizing + this.content.addEventListener('click', (e) => { + if (e.target.tagName === 'IMG') { + e.preventDefault(); + this.selectImage(e.target); + } else if (!e.target.closest('.rich-editor-resize-wrapper')) { + this.deselectImage(); + } + }); + + // Deselect image when clicking outside + document.addEventListener('click', (e) => { + if (!this.content.contains(e.target)) { + this.deselectImage(); + } + }); } showDropOverlay() { @@ -468,6 +566,13 @@ const img = document.createElement('img'); img.src = result.url; img.alt = file.name; + + // Store original dimensions when image loads + img.onload = () => { + img.dataset.originalWidth = img.naturalWidth; + img.dataset.originalHeight = img.naturalHeight; + }; + placeholder.replaceWith(img); } else { const link = document.createElement('a'); @@ -636,6 +741,152 @@ focus() { this.content.focus(); } + + selectImage(img) { + if (this.state.readOnly) return; + + // Deselect previous image + this.deselectImage(); + + this.state.selectedImage = img; + img.classList.add('selected'); + + // Wrap image in resize container if not already wrapped + if (!img.parentElement.classList.contains('rich-editor-resize-wrapper')) { + const wrapper = document.createElement('div'); + wrapper.className = 'rich-editor-resize-wrapper'; + wrapper.contentEditable = false; + + // Store original dimensions if not set + if (!img.dataset.originalWidth) { + img.dataset.originalWidth = img.naturalWidth; + img.dataset.originalHeight = img.naturalHeight; + } + + img.parentNode.insertBefore(wrapper, img); + wrapper.appendChild(img); + } + + const wrapper = img.parentElement; + + // Add resize handles + const corners = ['nw', 'ne', 'sw', 'se']; + corners.forEach(corner => { + const handle = document.createElement('div'); + handle.className = `rich-editor-resize-handle ${corner}`; + handle.dataset.corner = corner; + + handle.addEventListener('mousedown', (e) => { + e.preventDefault(); + e.stopPropagation(); + this.startResize(e, img, corner); + }); + + wrapper.appendChild(handle); + }); + + // Add size input + const sizeInput = document.createElement('div'); + sizeInput.className = 'rich-editor-size-input'; + sizeInput.innerHTML = ` + + + + `; + + const widthInput = sizeInput.querySelector('.width-input'); + widthInput.addEventListener('input', (e) => { + const newWidth = parseInt(e.target.value); + if (newWidth && newWidth > 0) { + this.resizeImage(img, newWidth); + } + }); + + wrapper.appendChild(sizeInput); + } + + deselectImage() { + if (!this.state.selectedImage) return; + + const img = this.state.selectedImage; + img.classList.remove('selected'); + + const wrapper = img.parentElement; + if (wrapper && wrapper.classList.contains('rich-editor-resize-wrapper')) { + // Remove resize handles and size input + wrapper.querySelectorAll('.rich-editor-resize-handle, .rich-editor-size-input').forEach(el => el.remove()); + + // Unwrap image + wrapper.parentNode.insertBefore(img, wrapper); + wrapper.remove(); + } + + this.state.selectedImage = null; + } + + startResize(e, img, corner) { + this.state.resizing = true; + + const startX = e.clientX; + const startY = e.clientY; + const startWidth = img.width; + const startHeight = img.height; + const aspectRatio = startWidth / startHeight; + + const onMouseMove = (e) => { + if (!this.state.resizing) return; + + let deltaX = e.clientX - startX; + let deltaY = e.clientY - startY; + + // Adjust delta based on corner + if (corner === 'nw' || corner === 'sw') { + deltaX = -deltaX; + } + if (corner === 'nw' || corner === 'ne') { + deltaY = -deltaY; + } + + // Use the larger delta to maintain aspect ratio + const delta = Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY; + const newWidth = Math.max(50, startWidth + delta); + + this.resizeImage(img, newWidth, aspectRatio); + }; + + const onMouseUp = () => { + this.state.resizing = false; + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + + // Update width input + const wrapper = img.parentElement; + const widthInput = wrapper.querySelector('.width-input'); + if (widthInput) { + widthInput.value = img.width; + } + + if (this.debouncedSave) this.debouncedSave(); + }; + + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + } + + resizeImage(img, newWidth, aspectRatio = null) { + if (!aspectRatio) { + const originalWidth = parseInt(img.dataset.originalWidth) || img.naturalWidth; + const originalHeight = parseInt(img.dataset.originalHeight) || img.naturalHeight; + aspectRatio = originalWidth / originalHeight; + } + + const newHeight = newWidth / aspectRatio; + + img.width = newWidth; + img.height = newHeight; + img.style.width = newWidth + 'px'; + img.style.height = newHeight + 'px'; + } } return { diff -r e07b4b5a66bb -r 240337164a80 s3/s3_uploader.c --- a/s3/s3_uploader.c Sun Feb 15 11:07:52 2026 -0800 +++ b/s3/s3_uploader.c Sun Feb 15 11:41:53 2026 -0800 @@ -153,24 +153,20 @@ } // Build canonical headers (must be sorted alphabetically) - char content_length_str[32]; - snprintf(content_length_str, sizeof(content_length_str), "%zu", data_length); - - size_t headers_len = 512 + strlen(host) + strlen(content_type) + strlen(content_length_str); + // Note: Content-Length is NOT signed for S3 PUT requests (it's sent but not in signature) + size_t headers_len = 512 + strlen(host) + strlen(content_type); char *canonical_headers = Dowa_Arena_Allocate(p_arena, headers_len); snprintf(canonical_headers, headers_len, - "content-length:%s\n" "content-type:%s\n" "host:%s\n" "x-amz-content-sha256:%s\n" "x-amz-date:%s\n", - content_length_str, content_type, host, payload_hash, ts.datetime); - const char *signed_headers = "content-length;content-type;host;x-amz-content-sha256;x-amz-date"; + const char *signed_headers = "content-type;host;x-amz-content-sha256;x-amz-date"; // Build canonical request char *canonical_request = s3__build_canonical_request("PUT", @@ -213,11 +209,18 @@ Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(url); Seobeo_Client_Request_Set_Method(p_req, "PUT"); Seobeo_Client_Request_Set_Body(p_req, (const char *)data, data_length); + Seobeo_Client_Request_Add_Header_Map(p_req, "Connection", "keep-alive"); Seobeo_Client_Request_Add_Header_Map(p_req, "Content-Type", content_type); Seobeo_Client_Request_Add_Header_Map(p_req, "x-amz-date", ts.datetime); Seobeo_Client_Request_Add_Header_Map(p_req, "x-amz-content-sha256", payload_hash); Seobeo_Client_Request_Add_Header_Map(p_req, "Authorization", auth_header); + Seobeo_Log(SEOBEO_DEBUG, "[S3] Uploading %zu bytes to %s\n", data_length, url); + Seobeo_Log(SEOBEO_DEBUG, "[S3] Content-Type: %s\n", content_type); + Seobeo_Log(SEOBEO_DEBUG, "[S3] x-amz-date: %s\n", ts.datetime); + Seobeo_Log(SEOBEO_DEBUG, "[S3] x-amz-content-sha256: %s\n", payload_hash); + Seobeo_Log(SEOBEO_DEBUG, "[S3] Authorization: %.80s...\n", auth_header); + Seobeo_Client_Response *p_resp = Seobeo_Client_Request_Execute(p_req); if (p_resp) diff -r e07b4b5a66bb -r 240337164a80 seobeo/s_network.c --- a/seobeo/s_network.c Sun Feb 15 11:07:52 2026 -0800 +++ b/seobeo/s_network.c Sun Feb 15 11:41:53 2026 -0800 @@ -268,9 +268,18 @@ uint32 offset = 0; while (offset < data_size) { - ssize_t n = write(p_handle->socket, - data + offset, - data_size - offset); + ssize_t n; + if (p_handle->ssl) + { + n = Seobeo_SSL_Write(p_handle, data + offset, data_size - offset); + } + else + { + n = write(p_handle->socket, + data + offset, + data_size - offset); + } + if (n==0) { // DEBUG