From d8111f6070f388d9f6b830468d15dc30a3eb788a Mon Sep 17 00:00:00 2001
From: icurfer <icurfer@gmail.com>
Date: Sat, 25 Jan 2025 22:45:27 +0900
Subject: [PATCH] =?UTF-8?q?minio=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?=
 =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4?=
 =?UTF-8?q?=ED=8A=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ...l => _unarchived_create_post_default.html} |  0
 blog/templates/blog/create_post.html          | 54 ++++++++++++-
 blog/templates/blog/create_post_bakcup.html   | 80 +++++++++++++++++++
 butler/migrations/0004_delete_noticeboard.py  | 16 ++++
 butler_ddochi/settings.py                     |  1 +
 butler_ddochi/urls.py                         |  1 +
 obs_minio/__init__.py                         |  0
 obs_minio/admin.py                            |  3 +
 obs_minio/apps.py                             |  6 ++
 obs_minio/migrations/__init__.py              |  0
 obs_minio/models.py                           |  3 +
 obs_minio/tests.py                            |  3 +
 obs_minio/urls.py                             |  8 ++
 obs_minio/views.py                            | 47 +++++++++++
 14 files changed, 219 insertions(+), 3 deletions(-)
 rename blog/templates/blog/{create_post_default.html => _unarchived_create_post_default.html} (100%)
 create mode 100644 blog/templates/blog/create_post_bakcup.html
 create mode 100644 butler/migrations/0004_delete_noticeboard.py
 create mode 100644 obs_minio/__init__.py
 create mode 100644 obs_minio/admin.py
 create mode 100644 obs_minio/apps.py
 create mode 100644 obs_minio/migrations/__init__.py
 create mode 100644 obs_minio/models.py
 create mode 100644 obs_minio/tests.py
 create mode 100644 obs_minio/urls.py
 create mode 100644 obs_minio/views.py

diff --git a/blog/templates/blog/create_post_default.html b/blog/templates/blog/_unarchived_create_post_default.html
similarity index 100%
rename from blog/templates/blog/create_post_default.html
rename to blog/templates/blog/_unarchived_create_post_default.html
diff --git a/blog/templates/blog/create_post.html b/blog/templates/blog/create_post.html
index e5736cd..d97db32 100644
--- a/blog/templates/blog/create_post.html
+++ b/blog/templates/blog/create_post.html
@@ -23,10 +23,8 @@
 
           <!-- 마크다운 에디터 -->
           <h2>Contents</h2>
+          <textarea id="markdown-editor" name="contents" class="form-control" rows="10"></textarea>
 
-          <div class="col-md-12">
-            {{ form.contents }}
-          </div>
           <!-- 버튼 -->
           <div class="d-flex justify-content-end mt-4">
             <button type="submit" class="btn btn-primary me-2">Create Post</button>
@@ -75,6 +73,56 @@
             hljs.highlightElement(block);
           });
       });
+
+      // Ctrl+V로 이미지 붙여넣기 처리
+      textarea.addEventListener("paste", async function (event) {
+        const items = (event.clipboardData || event.originalEvent.clipboardData).items;
+
+        for (const item of items) {
+          if (item.type.startsWith("image/")) {
+            const file = item.getAsFile();
+            if (!file) 
+              return;
+            
+            const formData = new FormData();
+            formData.append("image", file);
+
+            try {
+              // 이미지 업로드 API 호출
+              const response = await fetch("/obs_minio/upload/", {
+                method: "POST",
+                body: formData
+              });
+
+              if (response.ok) {
+                const data = await response.json();
+                const imageUrl = data.url; // 업로드된 이미지 URL
+
+                // 마크다운 에디터에 이미지 삽입
+                const markdownImage = `![Image](${imageUrl})\n`;
+                textarea.value += markdownImage;
+
+                // 미리보기 업데이트
+                const markdownContent = textarea.value;
+                const renderedContent = md.render(markdownContent);
+                preview.innerHTML = renderedContent;
+
+                // Highlight.js로 코드 블록 강조
+                document
+                  .querySelectorAll("#preview pre code")
+                  .forEach((block) => {
+                    hljs.highlightElement(block);
+                  });
+              } else {
+                alert("Image upload failed. Please try again.");
+              }
+            } catch (error) {
+              console.error("Error uploading image:", error);
+              alert("An error occurred during image upload.");
+            }
+          }
+        }
+      });
     </script>
   </div>
 {% endblock %}
diff --git a/blog/templates/blog/create_post_bakcup.html b/blog/templates/blog/create_post_bakcup.html
new file mode 100644
index 0000000..e5736cd
--- /dev/null
+++ b/blog/templates/blog/create_post_bakcup.html
@@ -0,0 +1,80 @@
+{% extends "components/base.html" %}
+{% block title %}Create Post{% endblock %}
+
+{% block main_area %}
+  <h1 class="pt-3">Create New Post</h1>
+  <div class="container">
+    <div class="row">
+      <div class="col-md-6">
+        <form method="POST" id="post-form">
+          {% csrf_token %}
+          <div class="mb-3">
+            {{ form.title.label_tag }}
+            {{ form.title }}
+          </div>
+          <div class="mb-3">
+            {{ form.summary.label_tag }}
+            {{ form.summary }}
+          </div>
+          <div class="mb-3">
+            {{ form.tags.label_tag }}
+            {{ form.tags }}
+          </div>
+
+          <!-- 마크다운 에디터 -->
+          <h2>Contents</h2>
+
+          <div class="col-md-12">
+            {{ form.contents }}
+          </div>
+          <!-- 버튼 -->
+          <div class="d-flex justify-content-end mt-4">
+            <button type="submit" class="btn btn-primary me-2">Create Post</button>
+            <a href="{% url 'blog:post_list' %}" class="btn btn-secondary">Cancel</a>
+          </div>
+        </form>
+      </div>
+      <div class="col-md-6">
+        <div id="preview" class="border p-3 bg-light h-100 overflow-auto"></div>
+      </div>
+    </div>
+
+    <!-- 마크다운 파서 및 스크립트 -->
+    <script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
+    <script>
+      // 마크다운 파서 초기화
+      const md = window.markdownit({
+        highlight: function (str, lang) {
+          if (lang && hljs.getLanguage(lang)) {
+            try {
+              return hljs
+                .highlight(str, {language: lang})
+                .value;
+            } catch (__) {}
+          }
+          return ''; // 기본 HTML 이스케이프 처리
+        }
+      });
+
+      // content 필드와 미리보기 연결
+      const textarea = document.getElementById("markdown-editor");
+      const preview = document.getElementById("preview");
+
+      // 실시간 미리보기 업데이트
+      textarea.addEventListener("input", function () {
+        const markdownContent = textarea.value; // textarea 내용 가져오기
+        const renderedContent = md.render(markdownContent); // 마크다운 -> HTML 변환
+        preview.innerHTML = renderedContent;
+
+        // Highlight.js로 코드 블록 강조
+        document
+          .querySelectorAll("#preview pre code")
+          .forEach((block) => {
+            hljs.highlightElement(block);
+          });
+      });
+    </script>
+  </div>
+{% endblock %}
diff --git a/butler/migrations/0004_delete_noticeboard.py b/butler/migrations/0004_delete_noticeboard.py
new file mode 100644
index 0000000..f561588
--- /dev/null
+++ b/butler/migrations/0004_delete_noticeboard.py
@@ -0,0 +1,16 @@
+# Generated by Django 4.2.14 on 2025-01-25 19:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('butler', '0003_rename_desc_ipmanagementrecord_contents'),
+    ]
+
+    operations = [
+        migrations.DeleteModel(
+            name='NoticeBoard',
+        ),
+    ]
diff --git a/butler_ddochi/settings.py b/butler_ddochi/settings.py
index b86905c..aa46220 100644
--- a/butler_ddochi/settings.py
+++ b/butler_ddochi/settings.py
@@ -51,6 +51,7 @@ INSTALLED_APPS = [
     'nhnc_mgmt',
     'mm_msg',
     'ansible_manager',
+    'obs_minio',
 ]
 
 MIDDLEWARE = [
diff --git a/butler_ddochi/urls.py b/butler_ddochi/urls.py
index 949e2b3..fcd3c32 100644
--- a/butler_ddochi/urls.py
+++ b/butler_ddochi/urls.py
@@ -30,6 +30,7 @@ urlpatterns = [
     path('nhnc_mgmt/', include('nhnc_mgmt.urls')),
     path('mm_msg/', include('mm_msg.urls')),
     path('ansible_manager/', include('ansible_manager.urls')),
+    path('obs_minio/', include('obs_minio.urls')),
 ]
 
 urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/obs_minio/__init__.py b/obs_minio/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/obs_minio/admin.py b/obs_minio/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/obs_minio/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/obs_minio/apps.py b/obs_minio/apps.py
new file mode 100644
index 0000000..d2bea7c
--- /dev/null
+++ b/obs_minio/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ObsMinioConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'obs_minio'
diff --git a/obs_minio/migrations/__init__.py b/obs_minio/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/obs_minio/models.py b/obs_minio/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/obs_minio/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/obs_minio/tests.py b/obs_minio/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/obs_minio/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/obs_minio/urls.py b/obs_minio/urls.py
new file mode 100644
index 0000000..9b719ea
--- /dev/null
+++ b/obs_minio/urls.py
@@ -0,0 +1,8 @@
+from django.urls import path
+from . import views
+
+app_name = 'obs_minio'
+
+urlpatterns = [
+    path('upload/', views.upload_image, name='upload_image'),
+]
diff --git a/obs_minio/views.py b/obs_minio/views.py
new file mode 100644
index 0000000..dd5ad2c
--- /dev/null
+++ b/obs_minio/views.py
@@ -0,0 +1,47 @@
+import uuid
+from django.http import JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from minio import Minio
+from minio.error import S3Error
+import urllib3
+
+# MinIO 설정
+MINIO_ENDPOINT = 'minio.icurfer.com:9000'
+MINIO_ACCESS_KEY = 'h5gOXcQieSE0kReVlpDa'
+MINIO_SECRET_KEY = '2S5vtc7DtrnUjqsAO6CF3kPVEqDtqmHgnt3OPIPY'
+BUCKET_NAME = 'butler-ddochi-image'
+
+@csrf_exempt
+def upload_image(request):
+    print("이미지업로드 동작시작")
+    if request.method == 'POST' and 'image' in request.FILES:
+        image = request.FILES['image']
+        unique_filename = f"uploads/{uuid.uuid4()}_{image.name}"
+
+        # MinIO 클라이언트 생성
+        client = Minio(
+            MINIO_ENDPOINT,
+            access_key=MINIO_ACCESS_KEY,
+            secret_key=MINIO_SECRET_KEY,
+            secure=True,
+            http_client=urllib3.PoolManager(cert_reqs='CERT_NONE')  # SSL 검증 비활성화
+        )
+
+        try:
+            # MinIO에 파일 업로드
+            client.put_object(
+                bucket_name=BUCKET_NAME,
+                object_name=unique_filename,
+                data=image,
+                length=image.size,
+                content_type=image.content_type
+            )
+
+            # Presigned URL 생성
+            presigned_url = client.presigned_get_object(BUCKET_NAME, unique_filename)
+            return JsonResponse({"url": presigned_url}, status=201)
+
+        except S3Error as e:
+            return JsonResponse({"error": str(e)}, status=500)
+
+    return JsonResponse({"error": "Invalid request"}, status=400)