sori.studio

์›Œ๋“œํ”„๋ ˆ์Šค ๊ธ€์— ์ด๋ฏธ์ง€ ์ถ”๊ฐ€ํ•˜๊ธฐ (์ปค์Šคํ…€ ํ•„๋“œ ํ™œ์šฉ)

๐Ÿ“˜ ์›Œ๋“œํ”„๋ ˆ์Šค REST API๋ฅผ ์ด์šฉํ•ด์„œ ๋‚˜๋งŒ์˜ ์‚ฌ์ดํŠธ ๋งŒ๋“ค๊ธฐ - 5ํŽธ

์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ๊ฒŒ์‹œ๋ฌผ์— ์—ฐ๊ฒฐํ•˜๊ธฐ (์ปค์Šคํ…€ ํ•„๋“œ ํ™œ์šฉ)

 

 

 

์ด์ œ ๋‹ค์‹œ ๊ธ€์“ฐ๊ธฐ๋กœ ๋Œ์•„์™€ ์‚ฌ์šฉ์ž๋“ค์ด ๊ธ€์„ ์ž‘์„ฑํ•  ๋•Œ ์ด๋ฏธ์ง€๋ฅผ ํ•จ๊ป˜ ์—…๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์งง์€ ํ…์ŠคํŠธ ์ค‘์‹ฌ์˜ ํŠธ์œ„ํ„ฐ ์Šคํƒ€์ผ ์›น ์•ฑ์ด์ง€๋งŒ, ์ด๋ฏธ์ง€๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด ์ฝ˜ํ…์ธ ์˜ ์ „๋‹ฌ๋ ฅ๊ณผ ๋ชฐ์ž…๋„๊ฐ€ ํ™•์‹คํžˆ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐ„๋‹จํ•˜๋ฉด์„œ๋„ ์ง๊ด€์ ์ธ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ดค์Šต๋‹ˆ๋‹ค.

 

๋ชฉํ‘œ

  • ์ด๋ฏธ์ง€ ์„ ํƒ ๋ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
  • ์ตœ๋Œ€ 8์žฅ๊นŒ์ง€ ์—…๋กœ๋“œ ์ œํ•œ
  • ์›Œ๋“œํ”„๋ ˆ์Šค Media API๋ฅผ ํ†ตํ•œ ์ด๋ฏธ์ง€ ๋“ฑ๋ก
  • image_urls๋ผ๋Š” ์ปค์Šคํ…€ ๋ฉ”ํƒ€ ํ•„๋“œ์— ์ด๋ฏธ์ง€ URL ์ €์žฅ

 

์™œ ์ปค์Šคํ…€ ๋ฉ”ํƒ€ ํ•„๋“œ๊ฐ€ ํ•„์š”ํ•œ๊ฐ€?

์›Œ๋“œํ”„๋ ˆ์Šค์—์„œ ๋Œ€ํ‘œ ์ด๋ฏธ์ง€(featured_media)๋Š” ํ•˜๋‚˜๋งŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ๋ณธ๋ฌธ(content)์— ์ด๋ฏธ์ง€๋ฅผ ์‚ฝ์ž…ํ•˜๋ฉด ํŽธ์ง‘์ด ๋ถˆํŽธํ•˜๋ฉฐ ๊ตฌ์กฐ์ ์œผ๋กœ ๊น”๋”ํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

์ €๋Š” ์ด๋ฏธ์ง€ URL์„ ๋ณ„๋„๋กœ ์ €์žฅํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์—์„œ ํ•ด๋‹น ํ•„๋“œ๋ฅผ ์ด์šฉํ•ด ์ด๋ฏธ์ง€๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ๋ Œ๋”๋งํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ์›ํ–ˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด์ฃ :

"meta": {
  "image_urls": [
    "https://myDomain.com/uploads/image1.jpg",
    "https://myDomain.com/uploads/image2.jpg",
    "https://myDomain.com/uploads/image3.jpg"
  ]
}

 

 

์ด๋ฏธ์ง€ URL ํ•„๋“œ๋ฅผ ์œ„ํ•œ ์ปค์Šคํ…€ ํ”Œ๋Ÿฌ๊ทธ์ธ

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ €๋Š” image_urls๋ผ๋Š” ์ปค์Šคํ…€ ๋ฉ”ํƒ€ ํ•„๋“œ๋ฅผ ๋“ฑ๋กํ•ด์ฃผ๋Š” ์ „์šฉ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ์—ญํ• ์€ ๋‹จ์ˆœํžˆ image_urls๋ผ๋Š” ๋ฉ”ํƒ€ ํ•„๋“œ๋ฅผ REST API์— ๋…ธ์ถœ์‹œํ‚ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

custom-image-meta.zip
0.00MB

 

๐Ÿ‘‰ ์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์›Œ๋“œํ”„๋ ˆ์Šค ๊ด€๋ฆฌ์ž > ํ”Œ๋Ÿฌ๊ทธ์ธ > ์ƒˆ๋กœ ์ถ”๊ฐ€ > ์—…๋กœ๋“œ ๋ฅผ ํ†ตํ•ด ์„ค์น˜ํ•œ ๋’ค ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

 

๐Ÿ“ HTML ๊ตฌ์กฐ

<textarea id="postContent" placeholder="๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ๋‚˜์š”?"></textarea>
<input type="file" id="imageUpload" multiple style="display: none" />
<button id="customUploadBtn" type="button">์ด๋ฏธ์ง€ ์„ ํƒ</button>
<div id="preview"></div>
<p id="fileStatus">0์žฅ์˜ ์ด๋ฏธ์ง€ ์„ ํƒ๋จ</p>
<button id="postButton" onclick="submitPost()">๊ฒŒ์‹œํ•˜๊ธฐ</button>

<p id="loading" class="loading" style="display: none">
  ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ค‘...<span id="progress">0/0</span>
</p>

 

1. input[type="file"]์€ ์ˆจ๊ฒจ๋‘๊ณ , ๋ฒ„ํŠผ์œผ๋กœ ํด๋ฆญ ์œ ๋„

2. ๋ฏธ๋ฆฌ๋ณด๊ธฐ(preview) ์˜์—ญ์— ์ด๋ฏธ์ง€์™€ ์‚ญ์ œ ๋ฒ„ํŠผ ํ‘œ์‹œ

3. ์—…๋กœ๋“œ ์ง„ํ–‰ ์ƒํƒœ๋„ progress๋กœ ํ‘œ์‹œ

 

 

๐Ÿง  ์ด๋ฏธ์ง€ ์„ ํƒ & ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ•จ์ˆ˜:

์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋กœ ๋ณด์—ฌ์ฃผ๊ณ , ๊ฐœ๋ณ„ ์‚ญ์ œ๋„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

let selectedImages = [];

function renderPreview() {
    const preview = document.getElementById('preview');
    preview.innerHTML = '';

    selectedImages.forEach((file, index) => {
        const reader = new FileReader();
        reader.onload = function (e) {
            const img = document.createElement('img');
            img.src = e.target.result;
            img.style.width = '80px';
            img.style.height = '80px';
            img.style.objectFit = 'cover';

            const removeBtn = document.createElement('button');
            removeBtn.innerText = 'โŒ';
            removeBtn.onclick = () => {
                selectedImages.splice(index, 1);
                renderPreview();
            };

            const container = document.createElement('div');
            container.style.display = 'inline-block';
            container.style.position = 'relative';
            container.appendChild(img);
            container.appendChild(removeBtn);

            preview.appendChild(container);
        };
        reader.readAsDataURL(file);
    });

    fileStatus.innerText = `${selectedImages.length}์žฅ์˜ ์ด๋ฏธ์ง€ ์„ ํƒ๋จ`;
}

 

 

๐Ÿ“ค ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ: ์›Œ๋“œํ”„๋ ˆ์Šค Media API ํ™œ์šฉ

fetch๋กœ ์›Œ๋“œํ”„๋ ˆ์Šค์˜ Media API์— ์ง์ ‘ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. 
์ด ๊ณผ์ •์—์„œ JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•ด ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€์˜ source_url์„ ๋ฐ˜ํ™˜๋ฐ›์Šต๋‹ˆ๋‹ค.

async function uploadImage(file, index, total) {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('title', file.name);
    formData.append('status', 'publish');

    document.getElementById('progress').innerText = `${index}/${total}`;

    const response = await fetch(mediaApiUrl, {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${token}`
        },
        body: formData
    });

    if (response.ok) {
        const data = await response.json();
        return data.source_url;
    } else {
        alert(`์ด๋ฏธ์ง€ ${index} ์—…๋กœ๋“œ ์‹คํŒจ!`);
        return null;
    }
}

 

 

๐Ÿ“ ๊ฒŒ์‹œ๋ฌผ ์ž‘์„ฑ ์‹œ ์ด๋ฏธ์ง€ URL ์ €์žฅ:

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๊ฐ€ ๋๋‚˜๋ฉด ํ•ด๋‹น URL๋“ค์„ ๋ฐฐ์—ด๋กœ ๋ชจ์•„์„œ ๊ฒŒ์‹œ๋ฌผ์— ์ €์žฅํ•˜๊ณ  REST API์— ์ปค์Šคํ…€ ๋ฉ”ํƒ€ ํ•„๋“œ๋กœ ์ „๋‹ฌ์„ ํ•ฉ๋‹ˆ๋‹ค.

const response = await fetch(apiUrl, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({
        title: `${userSlug}-${getCurrentTimestamp()}`,
        content,
        status: 'publish',
        meta: {
            image_urls: JSON.stringify(imageUrls),
        },
    }),
});

 

 

์‹ค์ œ๋กœ ๊ตฌํ˜„๋œ ๋ถ€๋ถ„ ์ƒ˜ํ”Œ