API จัดการ CMS
หลัง 1.0 API ที่มีเวอร์ชันสำหรับสคริปต์ บรรทัดคำสั่ง และเอเจนต์ AI ที่จัดการเนื้อหา โมเดล และการตั้งค่าของไซต์หนึ่งไซต์ ใช้บริการ lifecycle การ apply โมเดล และ handler ชุดเดียวกับหน้าผู้ดูแล ไม่ใช่ CRUD API ของแบ็กเอนด์ที่สร้างขึ้น การเปลี่ยนโมเดลทำได้โดยตรงด้วย schema:write หรือเสนอให้ผู้ใช้นำไปใช้ใน ตัวสร้างเนื้อหา
- สัญญาที่เครื่องอ่านได้:
openapi-v1.json(OpenAPI 3.1) - ชนิดข้อมูล TypeScript:
backend/src/lib/management/client-v1.generated.ts - บรรทัดคำสั่งและเซิร์ฟเวอร์ MCP:
dee-wan-manage.mjsดาวน์โหลดได้จาก การตั้งค่า → API - MCP ระยะไกล:
POST /api/manage/mcpการดำเนินการชุดเดียวกันผ่าน HTTPS โดยไม่ต้องดาวน์โหลด - ทั้งสามสร้างจาก
backend/src/lib/management/contract.tsด้วยnpm run docs:management-apiและnpm run check:management-apiจะล้มเหลวเมื่อไฟล์ใดไฟล์หนึ่งล้าสมัย
ข้อมูลรับรอง
หัวข้อที่มีชื่อว่า “ข้อมูลรับรอง”สร้างได้ที่ การตั้งค่า → API → ข้อมูลประจำตัวสำหรับจัดการ ต้องมีสิทธิ์ site:settings บนไซต์
- ตั้งชื่อ เลือกขอบเขตที่ใช้ได้ และเลือกโมเดลที่แตะได้หากต้องการ
- ให้ได้เฉพาะสิ่งที่คุณมีอยู่ตอนนี้ ขอบเขตหรือโมเดลที่คุณไม่มีจะถูกปฏิเสธด้วย
422 grant_not_heldพร้อมรายชื่อ และฟอร์มจะปิดตัวเลือกเหล่านั้น - secret แสดงครั้งเดียว ระบบเก็บเฉพาะ digest SHA-256 ของมัน คัดลอกก่อนปิดแผง
- รูปแบบ:
dwm_ตามด้วยอักขระ base64url 43 ตัว - เพิกถอนได้ที่หน้าจอเดียวกัน secret ที่ถูกเพิกถอนจะใช้ไม่ได้ตั้งแต่คำขอถัดไป
- ไม่มีตัวนับการใช้งานหรือเวลาที่ใช้ล่าสุด
ข้อมูลรับรองทำงานในนามผู้สร้าง ทุกคำขอ API จะอ่านสิทธิ์ปัจจุบันของผู้นั้นบนไซต์อีกครั้ง แล้วจำกัดให้เหลือเฉพาะส่วนที่ข้อมูลรับรองอนุญาตด้วย:
- ขอบเขต จำกัดว่าเรียกคำสั่งใดได้
- โมเดล (ถ้าเลือก) จำกัดว่าอ่านหรือเขียนโมเดลเนื้อหาใดได้
- บทบาทของผู้สร้าง จำกัดส่วนที่เหลือ รวมถึงกฎความเป็นเจ้าของ
update:ownสิทธิ์ที่แต่ละการเปลี่ยนสถานะ workflow ต้องการ และข้อจำกัดฟิลด์และภาษาของบทบาท (บุคคลและสิทธิ์เข้าถึง): การแสดงรายการและการอ่านรายการไม่รวมภาษาและฟิลด์ที่บทบาทอ่านไม่ได้ และการเขียนนอกบทบาทจะตอบfield_deniedหรือlocale_denied
หากผู้สร้างถูกปิดใช้งานหรือเสียสิทธิ์ในไซต์ ข้อมูลรับรองจะตอบ creator_unauthorized หากผู้สร้างเสียสิทธิ์ใด ข้อมูลรับรองก็เสียสิทธิ์นั้นในทันที ผู้สร้างที่เป็น superadmin ไม่ได้รับการข้ามสิทธิ์ ข้อมูลรับรองยังมีเพียงขอบเขตและโมเดลของตัวเอง
| ขอบเขต | อนุญาต | ต้องมีจากผู้สร้าง |
|---|---|---|
content:read |
อ่านไซต์ โมเดล รายการ เวอร์ชัน รายการที่อ้างอิง ข้อเสนอ หน้า และสื่อ รวมอยู่เสมอ | read บนโมเดล |
content:write |
สร้างและแก้ไขฉบับร่าง เพิ่มภาษา กู้คืนเวอร์ชันเก่า เปลี่ยนรายการที่ลิงก์ (relate, related from) | create / update บนโมเดล และ content:restore สำหรับการกู้คืนเวอร์ชัน |
content:propose |
เสนอการเปลี่ยนฟิลด์ให้บรรณาธิการตรวจ | read บนโมเดล |
pages:write |
บันทึกในตัวแก้ไขหน้า | update บนโมเดลหน้า |
media:write |
อัปโหลดสื่อ แก้ไขข้อความแทนภาพ จุดโฟกัส การโหลด และลำดับความสำคัญในการดึงของไฟล์ ลบไฟล์ที่ไม่ได้ใช้ และสร้าง เปลี่ยนชื่อ ย้าย และลบโฟลเดอร์ในคลังสื่อ รวมถึงย้ายไฟล์ระหว่างโฟลเดอร์ | media:upload สำหรับอัปโหลด media:update สำหรับแก้ไขและโฟลเดอร์ media:delete สำหรับลบ |
lifecycle:publish |
เผยแพร่และยกเลิกการเผยแพร่ โดยไม่ต้องมีคนยืนยันซ้ำ | update และสิทธิ์ของการเปลี่ยนสถานะ |
content:trash |
ย้ายรายการหรือภาษาเดียวไปถังขยะ และกู้คืน | delete บนโมเดล |
content:purge |
ลบรายการในถังขยะถาวร | delete บนโมเดล |
workflow:move |
ขั้นตอน workflow ที่ไม่เผยแพร่และไม่ยกเลิกการเผยแพร่ (ส่งตรวจทาน) | update และสิทธิ์ของขั้นตอน |
workflow:assign |
กำหนดผู้ดำเนินการถัดไป และดูรายชื่อผู้ที่กำหนดได้ | update บนโมเดล |
settings:write |
อ่านและเปลี่ยนการตั้งค่าไซต์: การเก็บเวอร์ชันและขั้นตอนตรวจทาน การใช้ภาษาสำรองเมื่อไม่มีคำแปล ไฟล์สื่อ การค้นหา ค่าเริ่มต้นของ Site Chrome, SEO, robots, การเปลี่ยนเส้นทาง และเมนู | site:settings ยกเว้นการค้นหาที่ใช้ site:infra แทน |
schema:propose |
เสนอโมเดลใหม่หรือการเปลี่ยนโมเดลให้ผู้ใช้ตรวจ แสดงรายการและอ่านข้อเสนอเหล่านั้น | model:apply |
schema:write |
สร้าง เปลี่ยน แทนที่ และลบโมเดลโดยตรง | model:apply สำหรับการลบต้องมี model:delete ด้วย |
data:import |
เรียกใช้การนำเข้าตั้งแต่ต้นจนจบ: แหล่งข้อมูล งาน การอัปโหลด การเดินหน้า การจับคู่ การหยุดชั่วคราว การทำต่อ การยกเลิก การย้อนกลับ บันทึกผลการนำเข้า และไฟล์ปัญหา ผลแต่ละอย่างยังถูกตรวจกับสิทธิ์ของผู้สร้างเอง | data:import และผลแต่ละอย่างต้องมีสิ่งที่การนำเข้าในหน้าแอดมินต้องใช้ด้วย (การเขียนโมเดลและรายการ media:upload model:apply site:settings และการเผยแพร่) |
data:export |
สร้างการส่งออก สร้างไฟล์ และอ่านไฟล์เป็นช่วงไบต์ | data:export |
roles:write |
สร้าง เปลี่ยน และลบบทบาทของไซต์ รวมถึงข้อจำกัดฟิลด์และภาษา ข้อมูลรับรองมอบได้เฉพาะสิ่งที่ตนมี | role:manage |
plugins:use |
แสดงปลั๊กอินที่ติดตั้ง สร้างลิงก์ใช้ครั้งเดียวที่เปิดหน้าของปลั๊กอินหรือการทำงานกับรายการ และเรียกใช้การดำเนินการที่ปลั๊กอินประกาศไว้ | สิ่งเดียวกับที่การเปิดหรือการเรียกใช้แบบนี้ต้องใช้ในหน้าแอดมิน: read บนรายการ (update สำหรับการดำเนินการที่ทำลายข้อมูล) หรือโมเดลที่ให้สิทธิ์ปลั๊กอินซึ่งผู้สร้างอ่านได้ รวมถึงอ่านได้เฉพาะรายการของตัวเอง; การดำเนินการระดับไซต์ต้องมี site:plugins |
/roles แสดงบทบาทของไซต์พร้อมชุดสิทธิ์ทั้งหมด: models, system และ limits ของฟิลด์และภาษาที่ไม่บังคับ (บุคคลและสิทธิ์เข้าถึง) การเขียนผ่านการตรวจเดียวกับ การตั้งค่า → บทบาท: สิทธิ์ที่ไม่รู้จักตอบ 400 invalid_permissions พร้อมทุกเหตุผล และชุดที่เกินข้อมูลรับรองตอบ 403 escalation_refused เพดานคือสิทธิ์ที่ถูกจำกัดของข้อมูลรับรองเอง ไม่ใช่บทบาททั้งหมดของบุคคลนั้น: ข้อมูลรับรองที่ไม่มี content:write ให้ update ไม่ได้ และที่ไม่มี lifecycle:publish ให้ content:publish ไม่ได้ wildcard ของโมเดล ("*") ไม่อยู่ในเพดานของข้อมูลรับรองเลย PUT /roles/{id} แทนที่เฉพาะฟิลด์ที่ระบุ ส่วน permissions แทนที่ทั้งชุด การลบปฏิเสธบทบาทตั้งต้น บทบาทที่มีคนถืออยู่ บทบาทที่กฎ Cloudflare Access ระบุ และบทบาทสุดท้ายที่ให้สิทธิ์เข้าถึงได้
- เส้นทางฐาน:
/api/manage/v1 - ส่ง
Authorization: Bearer <secret>ไซต์มาจากข้อมูลรับรอง คำขอที่ส่งX-Site-Idหรือระบุฐานข้อมูลจะถูกปฏิเสธ - เนื้อหา JSON เป็นแบบปิด คีย์ที่ไม่รู้จักให้ผล
invalid_request - ทุกการเขียนต้องมี
Idempotency-Key: อักขระ ASCII ที่มองเห็นได้ 1–128 ตัว (0x21–0x7Eห้ามมีช่องว่าง) ข้อยกเว้นไม่รับคีย์ และช่อง Key ในตารางด้านล่างว่างไว้: การ launch ปลั๊กอิน การตรวจ redirect (ไม่เขียนอะไรเลย) และสี่ route การแก้ไขภายใต้/entries/{id}/editingเมื่อลองใหม่ให้ใช้คีย์และเนื้อหาเดิม ผลลัพธ์แรกจะกลับมาพร้อมreplayed: true(หน้า:created: false) คีย์ที่ใช้กับคำขออื่นให้ผลidempotency_key_reusedคีย์ของการเผยแพร่ การยกเลิกการเผยแพร่ และการเลื่อนขั้นตอนเก็บไว้ 30 วัน คีย์อื่นเก็บไว้ 24 ชั่วโมง คีย์ของถังขยะ การกู้คืน การลบถาวร การมอบหมาย และภาษาใหม่เก็บคำตอบแรกไว้ตรงตัว การลองลบถาวรซ้ำจึงได้คำตอบเดิมแม้รายการจะไม่มีแล้ว - คำสั่งจองคีย์ก่อนเปลี่ยนแปลงสิ่งใด คำขอที่สองที่มีคีย์และ body เดียวกันขณะที่คำขอแรกยังทำงานอยู่จะไม่เปลี่ยนอะไรและได้
409 idempotency_in_progressพร้อมRetry-Afterให้ลองใหม่ด้วยคีย์และ body เดิมเพื่อรับคำตอบแรก คำสั่งที่ถูกปฏิเสธหรือล้มเหลวจะปล่อยคีย์ การแก้ metadata ของสื่อ การลบสื่อ การเขียนการตั้งค่า ข้อเสนอ และทุกขั้นของการนำเข้าและส่งออกทำตามกฎนี้ การเล่นซ้ำตอบด้วยคำตอบแรกทุกประการ ไม่ใช่การอ่านใหม่ - เพียงเวลาที่ผ่านไปไม่ได้ทำให้เรียกคำสั่งที่จองคีย์ไว้อีกครั้งได้อย่างปลอดภัย เมื่อคำขอที่จองคีย์หยุดตอบ การลองใหม่ครั้งถัดไปด้วยคีย์นั้นจะตัดสินการจองด้วยสิ่งที่คำสั่งพิสูจน์ได้เอง:
- เขียนข้อมูลและคำตอบไว้ในชุดคำสั่งเดียวกัน: การอัปโหลด แก้ไข และลบสื่อ โฟลเดอร์สื่อ การเขียนโมเดลและเมนู การตั้งค่า Site Chrome และ SEO ถังขยะ การกู้คืน การลบถาวร การมอบหมาย ภาษาใหม่ การสร้างการนำเข้า การส่งออก แหล่งนำเข้า หรือบทบาท การลบบทบาท และการนำเข้ารายการจากแหล่งสื่อ การจองที่ไม่มีคำตอบพิสูจน์ว่ายังไม่มีอะไรถูกเขียน การลองใหม่จึงทำคำสั่ง เมื่อการเขียนสำเร็จแต่คำตอบหายไป การลองใหม่จะตอบจากสิ่งที่เขียนไว้ พร้อม
replayed: trueไฟล์ที่เก็บไว้ระหว่างการอัปโหลดที่หยุดกลางทางจะถูกลบ - เก็บไว้ในบันทึกของตัวเอง หรือทำซ้ำได้อย่างปลอดภัย: การบันทึกรายการ การเผยแพร่ การยกเลิกเผยแพร่ การย้าย ข้อเสนอ การบันทึกหน้า การตั้งค่า operation ของปลั๊กอิน ส่วนของการนำเข้า การอัปเดตบทบาท การตั้งค่าความคิดเห็น การกลั่นกรองและการลบ การลองใหม่จะทำคำสั่งอีกครั้ง การกลั่นกรองหรือการลบที่ทำซ้ำหลังจากครั้งแรกสำเร็จแล้วจะตอบ
conflict(การลบ:not_found) พร้อมสถานะปัจจุบันของความคิดเห็น - ขั้นของงาน: ขั้นการส่งออกและนำเข้า ตัวเลือกในการตรวจทาน การหยุดชั่วคราว การยกเลิก และการปิดการอัปโหลด การจองบันทึกว่างานอยู่ที่ใดก่อนขั้นนั้น ถ้างานขยับไปแล้ว การลองใหม่จะตอบสถานะปัจจุบันพร้อม
replayed: trueและไม่ทำอะไร ถ้างานยังไม่ขยับ ขั้นการส่งออก ตัวเลือกในการตรวจทาน การหยุดชั่วคราว การยกเลิก และการปิดการอัปโหลดจะทำอีกครั้ง เพราะแต่ละอย่างเป็น compare-and-set บนตำแหน่งนั้นหรือกำหนดสถานะเป้าหมายadvanceImport,resumeImport,advanceImportUndoและretryImportUndoจะทำอีกครั้งก็ต่อเมื่อคำขอแรกจบด้วยข้อผิดพลาด ถ้าคำขอแรกหยุดตอบ (หมดเวลา การเชื่อมต่อขาด) การจองจะค้างอยู่
- เขียนข้อมูลและคำตอบไว้ในชุดคำสั่งเดียวกัน: การอัปโหลด แก้ไข และลบสื่อ โฟลเดอร์สื่อ การเขียนโมเดลและเมนู การตั้งค่า Site Chrome และ SEO ถังขยะ การกู้คืน การลบถาวร การมอบหมาย ภาษาใหม่ การสร้างการนำเข้า การส่งออก แหล่งนำเข้า หรือบทบาท การลบบทบาท และการนำเข้ารายการจากแหล่งสื่อ การจองที่ไม่มีคำตอบพิสูจน์ว่ายังไม่มีอะไรถูกเขียน การลองใหม่จึงทำคำสั่ง เมื่อการเขียนสำเร็จแต่คำตอบหายไป การลองใหม่จะตอบจากสิ่งที่เขียนไว้ พร้อม
- การจองที่ค้างอยู่จะตอบ
409 idempotency_in_progressโดยไม่มีRetry-Afterการรอจะไม่ทำให้มันจบ ให้ตรวจผลก่อน (สำหรับการนำเข้าใช้getImportJob) แล้วส่งคำสั่งอีกครั้งด้วยคีย์ใหม่ถ้ายังต้องการ การจองหมดอายุพร้อมคีย์หลัง 24 ชั่วโมง - การอัปโหลดสื่อรับไฟล์ที่มีชนิดและรูปแบบที่ไซต์รับ (การตั้งค่า → เว็บไซต์ → ไฟล์สื่อ): รูปภาพ เอกสาร เสียง วิดีโอ หรือไฟล์อื่นเป็นไฟล์ดาวน์โหลด การตรวจเดียวกันใช้กับแอดมิน API นี้ ปลั๊กอิน การนำเข้า และสำเนาไซต์ ชนิดถูกตรวจกับไบต์ ไฟล์ที่ไบต์ นามสกุล หรือชนิดที่ประกาศบอกว่าเป็นโปรแกรม สคริปต์ หรือมาร์กอัปจะถูกปฏิเสธ (
executable_fileหรือunsupported_media_typeสำหรับมาร์กอัป) สื่อแต่ละรายการตอบkindของตน:image,document,audio,videoหรือfilewidthและheightของรูปภาพมาจาก header ของรูปภาพเอง ฟิลด์widthและheightใช้เฉพาะเมื่อ header ไม่ได้ระบุ edited_fromอัปโหลดไบต์เป็นสำเนาที่แก้ไขของไฟล์ในคลัง (เช่น การครอปที่ทำในเครื่องมืออื่น) ต้นฉบับถูกเก็บไว้ สำเนาตอบedited_fromรับแท็กของต้นฉบับ และรับ alt text และโฟลเดอร์ของต้นฉบับ เว้นแต่คำขอระบุไว้ id ที่ไม่รู้จักคือ404media_edit_source_not_found- การอัปโหลดสื่อผ่าน API นี้โหลดทั้งไฟล์ไว้ในหน่วยความจำ จึงรับได้ไม่เกิน 32 MiB ไม่ว่าเพดานของชนิดจะเป็นเท่าใด แอดมินสตรีมเอกสาร เสียง วิดีโอ และไฟล์อื่นที่ใหญ่กว่าได้ถึง
limits.streamed_max_bytes: 100 MB หรือขีดจำกัดแพ็กเกจที่ใหญ่กว่าที่การ deploy ประกาศในMEDIA_REQUEST_BODY_LIMIT_BYTES(สูงสุด 5 GB ดู Workers limits) - การอัปโหลดสื่อผูกคีย์กับไบต์ ชื่อ และชนิดของไฟล์ รวมถึงฟิลด์
alt_texts,width,height,aspectRatio,on_duplicate,folder_idและedited_fromไม่ใช่ขอบเขต multipart คีย์บันทึกใน batch เดียวกับแถวสื่อ การลองใหม่ที่ตรงกันจึงคืนผลที่บันทึกไว้ - เมื่อไซต์มีไบต์เดียวกันอยู่แล้ว
on_duplicateเป็นตัวตัดสินask(ค่าเริ่มต้น) ตอบ409media_duplicateพร้อมไฟล์ที่มีอยู่ในexistingและจำนวนสำเนาในcopiesโดยไม่เก็บและไม่บันทึกอะไรuse_existingตอบ200พร้อมไฟล์ที่มีอยู่และreused: trueการลองใหม่จะส่งซ้ำเป็น200copyอัปโหลดสำเนาแยก ไฟล์ของไซต์อื่นไม่เคยถูกจับคู่ - คลังสื่อใช้ร่วมทั้งไซต์ ข้อมูลรับรองที่จำกัดเฉพาะบางโมเดลยังเห็นและอัปโหลดสื่อทั้งหมดได้
- สื่อทุกรายการตอบ
folder_idของตัวเอง หรือnullสำหรับ Unfiled การอัปโหลดระบุfolder_idได้ ไฟล์ซ้ำที่ถูกนำกลับมาใช้จะอยู่ที่เดิม โฟลเดอร์เป็นแบบตรรกะ: ไม่มีไฟล์ที่เก็บไว้หรือที่อยู่ใดถูกย้ายเลย /media/foldersตอบทั้งต้นไม้เรียงแบบ depth first แต่ละโฟลเดอร์มีrevision,depth,media_countของตัวเอง,subtree_folder_countและsubtree_media_countพร้อมunfiled,totalและขีดจำกัด: 5 ระดับ 500 โฟลเดอร์ต่อไซต์ ชื่อยาว 1–80 ตัวอักษรและไม่ซ้ำภายใต้โฟลเดอร์แม่โดยไม่สนตัวพิมพ์เล็กใหญ่ การเปลี่ยนชื่อและการย้ายส่งexpected_revisionการลบส่งexpected_revision,expected_foldersและexpected_mediaตามที่อ่านมา ค่าที่ล้าสมัยได้409media_folder_changedพร้อมfolderปัจจุบัน การปฏิเสธอื่น:media_folder_exists(พร้อมfolderที่ใช้ชื่อนั้นอยู่),media_folder_cycle,media_folder_too_deep,media_folder_limit,media_folder_not_foundการลบโฟลเดอร์จะลบทุกโฟลเดอร์ที่อยู่ข้างใต้และไม่ลบไฟล์ใดเลย: ทุกไฟล์จะกลายเป็น Unfiled/media/moveย้ายไฟล์ 1–200 ไฟล์ไปไว้ในโฟลเดอร์เดียว หรือไปที่ Unfiled ด้วยfolder_id: null- สื่อทุกรายการตอบ
default_focal({ x, y }เป็นสัดส่วน หรือnull),loadingและfetch_priorityด้วยGET /media/{id}ตอบสื่อหนึ่งรายการในรูปแบบเดียวกับในรายการ GET /media/{id}/usageตอบว่าไฟล์ถูกใช้ที่ไหน ตามที่คลังสื่อแสดง:used_by(รายการที่ข้อมูลรับรองนี้อ่านได้ ไม่เกิน 50 รายการ),hidden(การอ้างอิงในเนื้อหาที่ข้อมูลรับรองนี้อ่านไม่ได้: นับรวมแต่ไม่อธิบายเลย),at_least,total(nullเมื่อการนับถูกจำกัดหรืออ่านไม่ได้),moreและunreadablePUT /media/{id}เปลี่ยนสิ่งที่คลังสื่อแก้ไขได้:alt_texts(รหัสภาษาไปยังข้อความ ภาษาที่ไม่ได้ส่งจะคงข้อความเดิม และ""ล้างข้อความ),default_focal(เฉพาะภาพnullล้างค่า ถ้าไม่ใช่ภาพจะได้400 media_framing_not_image),loading(eager,lazy) และfetch_priority(high,low,auto) ต้องส่งอย่างน้อยหนึ่งฟิลด์ ตัวไฟล์ ขนาดภาพ และที่อยู่ของไฟล์ไม่เคยเปลี่ยนที่นี่POST /media/{id}/replaceรับ multipart ที่มีfileและคีย์ idempotency ไฟล์ใหม่ต้องเป็นชนิดเดียวกับไฟล์เดิม ระบบเก็บรหัสไฟล์ การอ้างอิงจากเนื้อหา โฟลเดอร์ แท็ก เครดิต และการจัดกรอบไว้ ค่าalt_texts,default_focal,loadingและfetchPriorityที่ไม่ได้ส่งมาจะคงเดิม ชื่อ ชนิด ขนาดไฟล์ และมิติจะอัปเดตตามไฟล์ที่ตรวจสอบแล้ว URL อาจเปลี่ยน ให้ใช้urlจากคำตอบ เวอร์ชันที่เผยแพร่และตรึงออบเจ็กต์เดิมไว้ยังเก็บไบต์เดิมจนกว่าจะไม่มีเวอร์ชันใดอ้างอิงออบเจ็กต์นั้น ไม่สามารถแทนที่ไฟล์ที่เชื่อมโยงจากต้นทางภายนอกได้ ใช้การตรวจสอบการอัปโหลดและการแทนที่แบบอะตอมมิกเดียวกับคลังสื่อ เมื่อลองอีกครั้งให้ใช้Idempotency-Keyไฟล์ และฟิลด์เดิม การดำเนินการ TypeScript คือreplaceMediaคำสั่ง CLI คือreplace-mediaและเครื่องมือ MCP คือreplace_mediaสำหรับ MCP ระยะไกลให้ส่งid,content_base64และfile_nameGET /media/{id}/originalsแสดงต้นฉบับปัจจุบันแยกตามภาษา (content:read) พร้อมidของออบเจ็กต์,lang,url, MIME type, ขนาดไบต์ และขนาดภาพ ไฟล์ส่วนตัวมีurlเป็นnullPUT /media/{id}/originals/{lang}รับ multipart ที่มีfileและexpected_object_idใช้สตริงว่างเพื่อสร้างต้นฉบับเฉพาะภาษาครั้งแรก มิฉะนั้นส่ง id ของออบเจ็กต์ปัจจุบันDELETEที่เส้นทางเดียวกันรับ JSON ที่มีexpected_object_idซึ่งไม่ใช่สตริงว่าง ทั้งสองคำขอต้องมีmedia:writeและIdempotency-Key; id ที่ล้าสมัยตอบ409 media_changedเปลี่ยนเฉพาะภาษาที่ระบุ ส่วน id สื่อ การอ้างอิง และต้นฉบับอื่นคงเดิม หากไม่มีต้นฉบับเฉพาะภาษา ระบบใช้ลำดับภาษาสำรองของไซต์ แล้วจึงใช้ต้นฉบับเริ่มต้น เวอร์ชันที่เผยแพร่ยังเก็บไบต์ที่ตรึงไว้ตามเดิมPOST /media/{id}/replaceยังคงแทนที่ต้นฉบับเริ่มต้น- TypeScript:
listMediaOriginals,replaceMediaOriginal,removeMediaOriginalCLI:list-media-originals,replace-media-original,remove-media-originalMCP:list_media_originals,replace_media_original,remove_media_original POST /media/{id}/removeลบไฟล์ที่ไม่มีอะไรอ้างอิง ระหว่างที่ยังมีการอ้างอิง จะตอบ409 media_in_useพร้อมฟิลด์การใช้งานชุดเดียวกัน และตอบmedia_usage_unreadableเมื่ออ่านการใช้งานไม่ได้ ในกรณีนั้นจะไม่มีอะไรถูกลบ เมื่อสำเร็จจะตอบstorage_cleanup:completeหรือpendingเมื่อระเบียนหายไปแล้วแต่ยังลบสำเนาที่เก็บไว้ไม่ได้ ไคลเอนต์ MCP เห็นการดำเนินการนี้เป็นแบบทำลายข้อมูลGET /media/sourcesแสดงแหล่งสื่อภายนอกที่ปลั๊กอินที่ใช้งานอยู่เสนอให้ไซต์ (ปลั๊กอินที่ได้รับmedia:source)GET /media/sources/{installationId}/{sourceId}/itemsตอบหนึ่งหน้าของแหล่ง ไม่เกิน 24 รายการ ค้นหาด้วยqส่งnext_cursorกลับเป็นcursorเพื่อรับหน้าถัดไป แต่ละรายการมีชื่อเรื่อง ชื่อไฟล์ ชนิด ขนาดthumbnail_urlบน origin ของปลั๊กอิน (โหลดโดยไม่มี referrer) และattributionแต่ละแหล่งแสดงmodes:copy,referenceหรือทั้งสอง การเรียกดูไม่เก็บอะไรPOST /media/sources/{installationId}/{sourceId}/importพร้อมitem_idและmodeเพิ่มหนึ่งรายการเข้าคลังmodeจำเป็นและเป็นหนึ่งในmodesของแหล่ง Dee Wan ไม่เดาเอง ด้วยcopyDee Wan ดึงไฟล์จากปลั๊กอินและตรวจเหมือนการอัปโหลดทั่วไป: ชนิด รูปแบบ และขีดจำกัดขนาดของไซต์ และไฟล์ใหม่ไม่ขึ้นกับแหล่งอีกเลย ด้วยreferenceDee Wan ไม่ดึงอะไรและเก็บ URL การส่งมอบของปลั๊กอินกับตัวตนของรายการ: สื่อตอบexternal: true,urlของมันคือ URL การส่งมอบ ไบต์ของมันแทนที่หรือแก้ไขไม่ได้ (media_external_bytes_unavailable, 409) และการลบมันเอาออกเฉพาะแถวในคลังfolder_idและon_duplicateทำงานเหมือนตอนอัปโหลด (สำหรับreferenceปลั๊กอิน แหล่ง และรายการเดียวกันคือรายการซ้ำ)use_existingตอบไฟล์ที่มีอยู่ด้วย200ไฟล์ใหม่ตอบattribution(ชื่อแหล่ง ผู้สร้าง สัญญาอนุญาต ลิงก์ และ id ของปลั๊กอิน แหล่ง และรายการ) สื่อทุกรายการตอบattributionเป็นnullสำหรับไฟล์ที่เพิ่มด้วยวิธีอื่น การปฏิเสธ:media_source_unavailable(404),media_source_mode_unsupported(422 แหล่งไม่ได้ประกาศmodeนั้น),plugin_refused(422 พร้อมrefusalของปลั๊กอิน),plugin_unreachable,plugin_response_invalidและmedia_source_file_unreachable(502),plugin_timeout(504) และการปฏิเสธของการอัปโหลด เช่นmedia_format_not_enabled,media_kind_not_enabledและmedia_too_largeบันทึกการตรวจสอบระบุว่าเป็นการอัปโหลดสื่อพร้อมข้อมูลแหล่งที่มา- จำกัดอัตรา: 120 คำขอต่อนาทีต่อข้อมูลรับรอง หากเกินจะได้
429พร้อมRetry-After
| เมธอด | เส้นทาง | ขอบเขต | คีย์ |
|---|---|---|---|
| GET | /site |
content:read |
|
| GET | /models |
content:read |
|
| GET | /entries?model_id=… |
content:read |
|
| POST | /entries |
content:write |
จำเป็น |
| GET | /entries/{id} |
content:read |
|
| PUT | /entries/{id} |
content:write |
จำเป็น |
| GET | /entries/{id}/versions |
content:read |
|
| GET | /entries/{id}/versions/{versionId} |
content:read |
|
| GET | /entries/{id}/dependents |
content:read |
|
| POST | /entries/{id}/publish |
lifecycle:publish |
จำเป็น |
| POST | /entries/{id}/unpublish |
lifecycle:publish |
จำเป็น |
| POST | /entries/{id}/proposals |
content:propose |
จำเป็น |
| GET | /entries/{id}/proposals |
content:read |
|
| GET | /entries/{id}/editing?locale=…&kind=… |
content:read |
|
| POST | /entries/{id}/editing/takeover |
content:write |
|
| PUT | /entries/{id}/editing/draft |
content:write |
|
| POST | /entries/{id}/editing/draft/discard |
content:write |
|
| GET | /pages/{id} |
content:read |
|
| POST | /pages/{id}/save |
pages:write |
จำเป็น |
| POST | /pages/{id}/translations |
pages:write |
จำเป็น |
| GET | /media |
content:read |
|
| POST | /media (multipart ส่วน file) |
media:write |
จำเป็น |
| GET | /media/folders |
content:read |
|
| POST | /media/folders |
media:write |
จำเป็น |
| PUT | /media/folders/{id} |
media:write |
จำเป็น |
| POST | /media/folders/{id}/remove |
media:write |
จำเป็น |
| POST | /media/move |
media:write |
จำเป็น |
| GET | /media/sources |
media:write |
|
| GET | /media/sources/{installationId}/{sourceId}/items |
media:write |
|
| POST | /media/sources/{installationId}/{sourceId}/import |
media:write |
จำเป็น |
| GET | /media/{id} |
content:read |
|
| GET | /media/{id}/usage |
content:read |
|
| PUT | /media/{id} |
media:write |
จำเป็น |
| POST | /media/{id}/replace (multipart file) |
media:write |
จำเป็น |
| GET | /media/{id}/originals |
content:read |
|
| PUT | /media/{id}/originals/{lang} (multipart file, expected_object_id) |
media:write |
จำเป็น |
| DELETE | /media/{id}/originals/{lang} |
media:write |
จำเป็น |
| POST | /media/{id}/remove |
media:write |
จำเป็น |
| GET | /languages |
content:read |
|
| POST | /entries/{id}/locales |
content:write |
จำเป็น |
| POST | /entries/{id}/versions/{versionId}/restore |
content:write |
จำเป็น |
| POST | /entries/{id}/transition |
workflow:move |
จำเป็น |
| GET | /entries/{id}/assignees |
workflow:assign |
|
| PUT | /entries/{id}/assignment |
workflow:assign |
จำเป็น |
| GET | /trash?model_id=… |
content:read |
|
| GET | /entries/{id}/trash |
content:read |
|
| POST | /entries/{id}/trash |
content:trash |
จำเป็น |
| POST | /entries/{id}/trash/restore |
content:trash |
จำเป็น |
| POST | /entries/{id}/trash/purge |
content:purge |
จำเป็น |
| POST | /entries/relate |
content:write |
จำเป็น |
| GET | /entries/{id}/related-from |
content:read |
|
| PUT | /entries/{id}/related-from |
content:write |
จำเป็น |
| GET | /menus |
settings:write |
|
| GET | /menus/{key} |
settings:write |
|
| PUT | /menus/{key} |
settings:write |
จำเป็น |
| POST | /menus/{key}/languages |
settings:write |
จำเป็น |
| GET | /models/{id} |
content:read |
|
| GET | /models/proposals?state=… |
schema:propose |
|
| POST | /models/proposals |
schema:propose |
จำเป็น |
| GET | /models/proposals/{id} |
schema:propose |
|
| POST | /models |
schema:write |
จำเป็น |
| PUT | /models/{id} |
schema:write |
จำเป็น |
| PUT | /models/{id}/folder |
schema:write |
จำเป็น |
| PUT | /models/{id}/definition |
schema:write |
จำเป็น |
| POST | /models/{id}/remove |
schema:write |
จำเป็น |
| GET | /settings/editorial |
settings:write |
|
| PUT | /settings/editorial |
settings:write |
จำเป็น |
| GET | /settings/language-fallback |
settings:write |
|
| PUT | /settings/language-fallback |
settings:write |
จำเป็น |
| GET | /settings/media-files |
settings:write |
|
| PUT | /settings/media-files |
settings:write |
จำเป็น |
| GET | /settings/search |
settings:write |
|
| PUT | /settings/search |
settings:write |
จำเป็น |
| GET | /settings/chrome |
settings:write |
|
| PUT | /settings/chrome |
settings:write |
จำเป็น |
| GET | /settings/seo |
settings:write |
|
| POST | /settings/seo/redirect-review |
settings:write |
|
| PUT | /settings/seo |
settings:write |
จำเป็น |
| GET | /imports/sources |
data:import |
|
| POST | /imports/sources |
data:import |
จำเป็น |
| GET | /imports?limit=… |
data:import |
|
| POST | /imports |
data:import |
จำเป็น |
| GET | /imports/{id} |
data:import |
|
| PUT | /imports/{id}/archive/parts/{part} (binary) |
data:import |
จำเป็น |
| POST | /imports/{id}/archive/complete |
data:import |
จำเป็น |
| POST | /imports/{id}/advance?recheck=… |
data:import |
จำเป็น |
| GET | /imports/{id}/findings |
data:import |
|
| GET | /imports/{id}/mapping |
data:import |
|
| PUT | /imports/{id}/mapping |
data:import |
จำเป็น |
| GET | /imports/{id}/wordpress |
data:import |
|
| PUT | /imports/{id}/wordpress |
data:import |
จำเป็น |
| GET | /imports/{id}/assignments |
data:import |
|
| PUT | /imports/{id}/assignments |
data:import |
จำเป็น |
| GET | /imports/{id}/principals |
data:import |
|
| PUT | /imports/{id}/principals |
data:import |
จำเป็น |
| POST | /imports/{id}/principals/accept-suggestions |
data:import |
จำเป็น |
| POST | /imports/{id}/pause |
data:import |
จำเป็น |
| POST | /imports/{id}/resume |
data:import |
จำเป็น |
| POST | /imports/{id}/cancel |
data:import |
จำเป็น |
| POST | /imports/{id}/undo |
data:import |
จำเป็น |
| POST | /imports/{id}/undo/retry |
data:import |
จำเป็น |
| GET | /imports/{id}/artifacts/{name} |
data:import |
|
| POST | /exports |
data:export |
จำเป็น |
| GET | /exports/{id} |
data:export |
|
| POST | /exports/{id}/advance |
data:export |
จำเป็น |
| GET | /exports/{id}/artifact?offset=…&length=… |
data:export |
|
| GET | /roles |
content:read |
|
| GET | /roles/{id} |
content:read |
|
| POST | /roles |
roles:write |
จำเป็น |
| PUT | /roles/{id} |
roles:write |
จำเป็น |
| POST | /roles/{id}/remove |
roles:write |
จำเป็น |
| GET | /plugins |
plugins:use |
|
| GET | /plugins/manage-views |
plugins:use |
|
| GET | /plugins/content-actions?instance_id=… |
plugins:use |
|
| POST | /plugins/{id}/manage-views/{view}/launch |
plugins:use |
|
| POST | /plugins/{id}/content-actions/{action}/entries/{instanceId}/launch |
plugins:use |
|
| GET | /plugins/operations?instance_id=… |
plugins:use |
|
| POST | /plugins/{id}/operations/{operation}/invoke |
plugins:use |
ต้องมี |
| GET | /rich-blocks |
content:read |
สี่ route การแก้ไขอ่านและเปลี่ยน lease การแก้ไขและ draft ร่วมของรายการและภาษาเดียว (ดู lease การแก้ไข และ draft ร่วม ใน ความขัดแย้ง) route เหล่านี้ไม่รับ Idempotency-Key และไม่ถูกบันทึกในประวัติ audit route เหล่านี้บังคับความเป็นเจ้าของและขอบเขตการอ่านฟิลด์ของผู้เรียก และตอบเฉพาะฟิลด์ที่ข้อมูลรับรองอ่านได้
สิทธิ์อ่านและสิทธิ์เขียนแยกกัน ผู้สร้างที่เปลี่ยนรายการได้แต่อ่านแถวหรือภาษานั้นไม่ได้ (อ่านได้เฉพาะของตนเองแต่แถวเป็นของคนอื่น หรือไม่มีสิทธิ์อ่านในภาษานั้น) ยัง takeover เขียน และทิ้ง draft ได้ แต่ไม่มีคำตอบใดมี draft มาด้วย: GET ถูกปฏิเสธ (not_owner หรือ locale_denied) takeover ตอบ draft: null 409 draft_conflict ตอบ current: null และ draft ที่เขียนแล้วจะส่งกลับเฉพาะสิ่งที่ผู้สร้างเขียนเอง ผู้สร้างที่มีสิทธิ์ลักษณะนี้จึงอ่าน revision ของฉบับร่างไม่ได้: ให้ทิ้งโดยไม่ระบุ revision แล้วเขียนด้วย expected_revision: 0 ผู้สร้างที่ไม่มีสิทธิ์อ่านโมเดลเลยจะถูกปฏิเสธด้วย 403 ในทุก route การแก้ไข และไม่มีอะไรเปลี่ยน
GET /entries/{id}/editing: querylocaleและkind(contentหรือpage)POST /entries/{id}/editing/takeover: body{ locale }PUT /entries/{id}/editing/draft: body{ locale, kind, baseline_version_id, expected_revision, data }baseline_version_idคือเวอร์ชันที่ draft เริ่มจาก หรือnull;dataคือค่าฟิลด์ของ draftPOST /entries/{id}/editing/draft/discard: body{ locale, kind }และrevisionที่ไม่บังคับ เพื่อทิ้งเฉพาะ revision นั้น
การปฏิเสธบน route เหล่านี้:
403 not_owner: ผู้สร้างอ่านหรือเปลี่ยนได้เฉพาะรายการของตนเอง และไม่ได้เป็นเจ้าของแถวของรายการในภาษานั้น (หรือตัวรายการเอง เมื่อภาษานั้นยังไม่มีแถว)403 locale_deniedพร้อมlocale: การอ่านต้องมีสิทธิ์อ่านในภาษานั้น takeover การเขียน draft และการทิ้ง draft ต้องมีสิทธิ์แก้ไขในภาษานั้น หรือสิทธิ์สร้างเมื่อรายการยังไม่มีแถวในภาษานั้น403 field_deniedพร้อมfields: ["slides"]: draft แบบpageสำหรับผู้สร้างที่อ่านส่วนต่าง ๆ ของหน้าไม่ได้ ผู้สร้างที่อ่านชื่อเรื่องของหน้าไม่ได้จะได้titleและbaseline.titleเป็น""และการเขียนของเขาจะคงชื่อเรื่องที่เก็บไว้423 lockedพร้อมholderและrecovery: อีกคนหนึ่งถือภาษานั้นอยู่ lease ถูกตรวจอีกครั้งภายในการเขียนหรือการทิ้ง หากมีผู้อื่นรับช่วงแก้ไขระหว่างนั้น การเขียนหรือทิ้งฉบับร่างจะถูกปฏิเสธด้วย และไม่มีอะไรเปลี่ยน การเขียนที่ถูกปฏิเสธจะถูกเก็บเป็น draft กู้คืนของผู้สร้างเอง (recovery) การทิ้งที่ถูกปฏิเสธไม่เก็บอะไรไว้:recovery: null409 draft_conflictพร้อมcurrent: revision ล้าสมัยcurrentและคำตอบอื่นทุกคำตอบที่มี draft มีเฉพาะฟิลด์ที่ผู้สร้างอ่านได้currentเป็นnullเมื่อไม่มี draft หรือเมื่อผู้สร้างอ่านแถวหรือภาษานั้นไม่ได้
/entries แสดงรายการหลักของกลุ่มคำแปลในโมเดลเดียว: model_id (จำเป็น), published, after, limit (1–50) ผู้สร้างที่อ่านได้เฉพาะรายการของตัวเองในโมเดลนั้นจะได้ listing_owner_restricted ให้อ่านรายการเหล่านั้นด้วย id แทน
POST /entries และ PUT /entries/{id} รับรายการความสัมพันธ์ใต้ relations (ฟิลด์ → รายการ) ฟิลด์ความสัมพันธ์ที่มีลิงก์ต่างกันในแต่ละภาษารับรายการเหล่านั้นใต้ locale_relations (ภาษา → ฟิลด์ → รายการ) แทน ดู ลิงก์ต่างกันในแต่ละภาษา ฟิลด์ความสัมพันธ์รับ { connect, disconnect, move } ด้วย โดยทำ disconnect ก่อน ตามด้วย connect แล้วจึง move รายการ connect เป็น id (เพิ่มที่ท้าย) หรือ { id, position } move คือ [{ id, position }] สำหรับรายการที่เกี่ยวข้องอยู่แล้ว position เป็นหนึ่งเดียวใน { before: id }, { after: id }, { start: true }, { end: true } จุดอ้างอิงคือ id ในรายการ ณ ขั้นนั้น การปฏิเสธ (422): relation_position_anchor_missing, relation_write_invalid, relation_write_duplicate (id อยู่ทั้งใน connect และ move ทั้งใน disconnect และ move หรือซ้ำสองครั้งใน move), relation_move_target_missing และ relation_position_not_ordered (ตำแหน่งหรือ move บนฟิลด์เป้าหมายเดียว) ตำแหน่งตีความกับรายการที่เก็บไว้ ณ ตอนบันทึก expected_current_version_id ยังปฏิเสธการบันทึกที่ล้าสมัย ทุกที่ที่การเขียนความสัมพันธ์รับ id จะรับ { model, id } ที่ระบุ slug ของ model เป้าหมายด้วย ดู ความสัมพันธ์กับหลายโมเดล
เชื่อมโยง และ เชื่อมโยงจาก เปลี่ยนรายการลิงก์ของภาษาที่ id ของรายการนั้นระบุ
/trash แสดงถังขยะของโมเดลเดียวในแบบเดียวกัน (model_id, after, limit) แต่ละรายการคือทั้งรายการ (scope: entry ระบุด้วยรายการหลัก) หรือภาษาเดียวที่ย้ายไปถังขยะแยกเอง (scope: language) พร้อม deleted_at และ purge_after (อีก 30 วัน)
/menus อ่านเมนูของ Site Chrome เวอร์ชันที่ยอมรับแล้ว และ revision ซึ่งคือเวอร์ชันที่ยอมรับแล้วที่เมนูมาจาก (null เมื่อยังไม่มีเวอร์ชันที่ยอมรับ) เมนูประกอบด้วย key, name และ variants: รหัสภาษาไปยัง items ของภาษานั้นเอง placed_in จับคู่ภาษากับตำแหน่งที่เมนูแสดงในภาษานั้น /menus/{key} เพิ่ม languages, served (ต่อภาษาของไซต์ คือเวอร์ชันที่ให้บริการในวันนี้ภายใต้กฎ strict หรือ fallback ของไซต์ หรือ null) และ live (ต่อเวอร์ชัน แล้วต่อ id ของรายการ คือภาษาที่รายการนั้นแสดงอยู่ในวันนี้) รายการที่หน้า โพสต์ หรือหน้ารวม (archive) ยังไม่เผยแพร่จะถูกซ่อนบนไซต์ PUT /menus/{key} สร้างหรือแทนที่เมนูหนึ่งเมนูพร้อมทุกเวอร์ชัน: expected_revision, name, variants และ placements ที่ไม่บังคับ (รหัสภาษาไปยัง header/footer ภาษาที่ไม่ได้ส่งจะคงตำแหน่งเดิม) แต่ละรายการชี้ไปที่ target: entry (model, entry = translation_group_id), blog, archive (role category/tag/author, entry), url (https), email, phone หรือ path หรือ null สำหรับหัวข้อที่มีรายการอยู่ข้างใต้ ทุกรายการต้องมีป้ายชื่อ id ของรายการไม่ซ้ำกันภายในเวอร์ชันเดียว POST /menus/{key}/languages เพิ่มหนึ่งเวอร์ชัน: expected_revision, language (ภาษาของไซต์) และ copy_from (ภาษาที่จะคัดลอก โดยได้ id ของรายการใหม่ หรือ null สำหรับเวอร์ชันว่าง) คำตอบอาจเป็น menu_not_found 404, menu_language_exists 409, menu_language_missing หรือ menu_language_unknown 422 การบันทึกแต่ละครั้งคือ Site Chrome เวอร์ชันใหม่ที่ยอมรับทันที การเปลี่ยนแปลง Site Chrome ที่ยังไม่ยอมรับจะถูกเขียนใหม่ทับด้านบนโดยรวมเมนูไว้ (draft: carried) หรือปล่อยไว้ให้คนจัดการ (draft: needs_rebase) expected_revision ที่ล้าสมัยได้ 409 stale_document พร้อม revision
การนำเข้าและส่งออก
หัวข้อที่มีชื่อว่า “การนำเข้าและส่งออก”การนำเข้าและส่งออกชุดเดียวกับใน การตั้งค่า → การย้ายข้อมูล แต่ไม่ต้องใช้หน้าแอดมิน ทุกขั้นตอนเรียกใช้โค้ดนำเข้าหรือส่งออกของหน้าแอดมินเองพร้อมการปฏิเสธแบบเดียวกัน ที่นี่ไม่ใช่เครื่องมือนำเข้าตัวที่สอง
การนำเข้า POST /imports พร้อม format (tar, tar.gz, json, transfer, wxr), source ใหม่ (label, system) หรือ source_id ที่มีอยู่ และ destination ที่ไม่บังคับ แต่ละไซต์นำเข้าได้ครั้งละหนึ่งงาน (409 import_already_active) จากนั้น:
- อัปโหลดไฟล์เป็นส่วน:
PUT /imports/{id}/archive/parts/{n}พร้อมContent-Type: application/octet-streamส่วนละ 5 MiB ทุกส่วนยกเว้นส่วนสุดท้ายต้องมีขนาด 5 MiB พอดี และเลขส่วนเริ่มที่ 1 ขีดจำกัดขนาดของรูปแบบไฟล์ยังมีผล หมายเลขส่วนถูกผูกกับ SHA-256 ของไบต์ก่อนเก็บไบต์ใด ทั้งที่นี่และในการอัปโหลดผ่านแอดมิน: การส่งไบต์เดิมซ้ำจะได้คำตอบเหมือนเดิม ไบต์ที่ต่างไปสำหรับส่วนที่ผูกแล้วจะได้409 import_part_conflictไม่ว่าคีย์จะเป็นอะไร และไม่มีไบต์ใดถูกเก็บ ระบบไม่ดึงข้อมูลจาก URL ใด ๆ เลย POST /imports/{id}/archive/completeโดยจะส่งclient_sha256ของทั้งไฟล์ด้วยก็ได้ สำหรับ.deewanแต่ละคำขอตรวจสอบไม่เกิน 32 MiB ขณะที่outcome.moreเป็นtrueให้เรียกadvanceหรือเรียกคำสั่งจบการอัปโหลดอีกครั้งด้วยคีย์ idempotency ใหม่ ระบบทำต่อจากจุดตรวจสอบที่บันทึกไว้โดยใช้ ID งาน และจะระบุclient_sha256_matchesหลังตรวจสอบทั้งไฟล์แล้วเท่านั้นPOST /imports/{id}/advanceไปเรื่อย ๆ จนคำตอบไม่ขออะไรเพิ่ม แต่ละครั้งเรียกใช้หนึ่งขั้นที่มีขอบเขตจำกัดบนเซิร์ฟเวอร์: เตรียมไฟล์ อ่านไฟล์ สร้างรายการงาน ตรวจสอบหรือนำเข้าหนึ่งชุด ขั้นที่ Worker ทำไม่เสร็จในคำขอเดียวจะทำต่อในการเรียกครั้งถัดไป ไม่มีการเรียกครั้งเดียวสำหรับการนำเข้าทั้งหมด- เมื่อ
decisionระบุบางอย่าง ให้ตอบสิ่งนั้นแล้วเดินหน้าอีกครั้ง: เลือกการจับคู่ของ Strapi (PUT /imports/{id}/mapping) ตัวเลือกของ WordPress (PUT /imports/{id}/wordpress) การกำหนดของประวัติไซต์ (PUT /imports/{id}/assignments) หรือแก้สิ่งที่GET /imports/{id}/findingsแสดง?recheck=trueตรวจสอบซ้ำหลังการตรวจสอบเสร็จแล้ว การโอนประวัติหรือการนำเข้า WordPress ยังจับคู่บุคคลที่ให้เครดิตกับสมาชิกที่นี่ได้:GET /imports/{id}/principalsแสดงแต่ละบุคคล (ref,labelต้นทาง,email,uses,suggestion,choice,mapped,problem) และmembersที่ใช้งานอยู่PUT /imports/{id}/principalsพร้อม{ "ref", "user_id" }จับคู่หนึ่งคน (user_id: nullให้เป็นป้ายต่อไป) ไฟล์.deewanแบ่งรายชื่อบุคคลและการมอบหมายเป็นหน้า ให้ส่งnext_cursorที่ได้รับโดยไม่แก้ไข พร้อมlimit(1–100 ค่าเริ่มต้น 50) แต่ละหน้าของรายชื่อบุคคลมีรายชื่อสมาชิกที่ใช้งานอยู่ให้เลือกด้วยPOST /imports/{id}/principals/accept-suggestionsยอมรับคำแนะนำที่อีเมลตรงกันเฉพาะหน้าที่เลือก: ใช้cursorและlimitเดียวกับ GET แล้วอ่านหน้าถัดไปด้วยnext_cursorจาก GET สำหรับ JSON แบบเดิมและ WordPress คำสั่งนี้ยอมรับคำแนะนำทั้งหมดที่ยังไม่ได้ตัดสินใจ ตัวเลือกที่บันทึกไว้แล้วจะคงเดิม สมาชิกต้องใช้งานอยู่บนไซต์นี้ (409 principal_user_ineligible)refที่ไม่รู้จักคือ404 principal_not_in_transferไม่มีอะไรสร้างหรือเปลี่ยนบัญชี การตรวจสอบจะบล็อกด้วยprincipal_mapping_invalidเมื่อสมาชิกที่จับคู่เสียสิทธิ์เข้าถึง และการรันจะตรวจอีกครั้งก่อนเขียน ดู การนำเข้าและการย้ายข้อมูล
การนำเข้าครั้งหลังจากต้นทางเดียวกันจะสร้างสิ่งที่ใหม่และข้ามสิ่งที่นำเข้าแล้ว ไม่เปลี่ยนเนื้อหาที่การนำเข้าครั้งก่อนเขียนเลย และการบันทึกการจับคู่ใหม่ไม่เปลี่ยนสิ่งที่นำเข้าแล้ว ข้อยกเว้นเดียวคือเมนู WordPress ที่ยังเหมือนที่การนำเข้าครั้งล่าสุดเขียนไว้ทุกประการ ซึ่งจะได้รายการที่ตอนนี้จับคู่ได้เพิ่ม (การนำเข้าและการย้ายข้อมูล) 5. การตรวจสอบที่ผ่านและเป็นปัจจุบันจะนำเข้าในการเดินหน้าครั้งถัดไป: การเดินหน้างานที่ตรวจสอบแล้วคือการอนุมัติให้เริ่ม
คำตอบของการนำเข้าทุกแบบ ยกเว้นไฟล์ผลลัพธ์ คือ { "data": state }:
job:id,status,stage(upload,prepare,review,check,import,undo,done),format,source,plan_revision,upload(state,parts_received,bytes_received,part_size),progress,dry_run_state,undo_state,final_status,errorและเวลาต่าง ๆfindings: จำนวนblocking,warningและinfodecision:nullหรือ{ kind, message }โดยkindเป็นหนึ่งในupload_needed,mapping_required,wordpress_mapping_required,findings_block,paused,blocked,cancel_pending,undo_refused,undo_failedmore: การเดินหน้าอีกครั้งจะเรียกใช้ขั้นได้ทันทีnext_actions: การดำเนินการที่ทำได้ตอนนี้stepและoutcome: สิ่งที่คำขอนี้เรียกใช้ หรือnull
pause หยุดหลังชุดที่กำลังทำงานอยู่ resume ทำต่องานที่หยุดชั่วคราว cancel เก็บสิ่งที่นำเข้าแล้วไว้และเขียนบันทึกผลการนำเข้าที่ระบุว่ายกเลิกแล้ว (decision.kind: cancel_pending ระหว่างที่การล้างไฟล์ยังไม่เสร็จ) POST /imports/{id}/undo ย้อนกลับหนึ่งชุดต่อการเรียกและเป็นการดำเนินการที่ทำลายข้อมูล undo/retry นำหน่วยที่ถูกปฏิเสธกลับเข้าคิวในกรณีที่หน้าแอดมินอนุญาต GET /imports/{id}/findings?limit=&cursor= อ่านผลการตรวจได้ไม่เกิน 500 รายการ (ค่าเริ่มต้น 100) เรียงจากร้ายแรงที่สุด แล้วตามลำดับที่พบ ส่ง next_cursor เป็น cursor เพื่ออ่านต่อ (null หลังรายการสุดท้าย) GET /imports/{id}/mapping, GET /imports/{id}/wordpress, GET /imports/{id}/assignments และ GET /imports/{id}/principals รับ limit (ไม่เกิน 500 ค่าเริ่มต้น 200) และ cursor แบบเดียวกัน รายการยาวของ endpoint เหล่านี้มีหนึ่งหน้าตามลำดับของแอดมิน: types ของ Strapi, normalized.skipped_fields, แต่ละรายการของ normalized.group_keys และ review.unresolved; posts, pages และ entries ของ WordPress; assignments ของประวัติ; principals และ members ส่วน totals นับแต่ละรายการทั้งหมด ณ ตำแหน่งของรายการ และฟิลด์อื่นทั้งหมด รวมถึง mapping มาครบทุกหน้า GET /imports/{id}/artifacts/{name}?offset=&length= ให้ช่วงไบต์ของ receipt, problems (problem-rows.csv), entries, media (แมนิเฟสต์), partition หรือ plugin_data (ไฟล์ข้อมูลปลั๊กอินของ WordPress ดู การนำเข้าและการย้ายข้อมูล) ซึ่งเป็นไบต์เดียวกับที่หน้าแอดมินดาวน์โหลด ไม่เกิน 4 MiB ต่อคำขอ เป็น application/octet-stream พร้อม header X-Artifact-* ด้านล่าง X-Artifact-Content-Type ระบุชนิดของไฟล์เอง งานที่ไม่มีอยู่และงานของไซต์อื่นตอบ 404 job_not_found เหมือนกัน
การส่งออก เรียก POST /exports พร้อม product (bundle, content, csv, xlsx, history) และ models, locale หากต้องการ จากนั้นเรียก POST /exports/{id}/advance จน more เป็น false สำเนาไซต์และการย้ายพร้อมประวัติใช้รูปแบบ .deewan ที่ทำต่อได้ ส่วนข้อมูลรายการใช้ JSON Lines แต่ละคำขอเก็บส่วนถัดไปที่แก้ไขไม่ได้ หรือทำขั้นตอนประกอบไฟล์ต่อ การประกอบอาจต้องเรียกหลายครั้ง เมื่อเสร็จแล้ว artifact จะระบุขนาดไฟล์และ SHA-256 ของทั้งไฟล์ แล้วจึงลบส่วนย่อย pages แสดง checksum, ขนาด, cursor และสถานะ readable ของแต่ละส่วน CSV และ XLSX ยังคงมีข้อจำกัดแยกต่างหาก
GET /exports/{id}/artifact?offset=&length= อ่านไฟล์ที่เสร็จแล้ว ไม่เกิน 4 MiB ต่อคำขอ ก่อนเสร็จจะได้ 409 export_not_final เพิ่ม page=<n> เพื่ออ่านหน้าที่เก็บไว้หนึ่งหน้าขณะที่การส่งออกยังทำงาน หน้าที่ถูกลบหลังเสร็จจะได้ 409 export_page_gone การอ่านทุกครั้งตอบ application/octet-stream พร้อม X-Artifact-Offset, X-Artifact-Length, X-Artifact-Total, X-Artifact-Sha256 และ X-Artifact-Content-Type ของทั้งไฟล์, X-Artifact-Next-Offset (ไม่มีเมื่อถึงท้ายไฟล์) และสำหรับหน้า X-Artifact-Page และ X-Artifact-Next-Page (ที่ท้ายหน้า เมื่อสร้างหน้าถัดไปแล้ว) ไฟล์ที่เก็บซึ่งไม่ใช่ไฟล์ที่บันทึกไว้ได้ 409 export_artifact_changed การอ่านเลยท้ายไฟล์ได้ 400 artifact_range_invalid การเดินหน้าสองครั้งพร้อมกันไม่ทำให้หน้าหายหรือซ้ำ: ครั้งที่สองตอบการส่งออกตามที่ครั้งแรกทิ้งไว้ การส่งออกที่หมดอายุและที่ล้มเหลวจะถูกปฏิเสธเหมือนในหน้าแอดมิน
ทุกการเขียนต้องมี Idempotency-Key ประวัติบันทึกการดำเนินการ งาน รูปแบบ ช่วง และผลลัพธ์ แต่ไม่บันทึกเนื้อหาไฟล์ ค่าเนื้อหา การจับคู่ ข้อความของสิ่งที่พบ หรือไบต์ที่ดาวน์โหลด
การตั้งค่าไซต์
หัวข้อที่มีชื่อว่า “การตั้งค่าไซต์”settings:write อ่านและเปลี่ยนการตั้งค่าผ่านข้อมูลที่มีชนิดกำกับ แยกตามส่วน ทุกการเขียนระบุ revision ที่อ่านมา หาก revision ล้าสมัยจะไม่เขียนอะไรเลย ทุกการเขียนใช้ตัวตรวจและค่าที่เก็บชุดเดียวกับหน้าการตั้งค่าของส่วนนั้น และบันทึก management.settings.update ไว้คู่กับบันทึกของหน้านั้นเอง ผู้สร้างต้องมี site:settings ในขณะนั้น ส่วนการค้นหาต้องมี site:infra แทน
| ส่วน | อ่าน | เขียน | Revision |
|---|---|---|---|
| การเก็บเวอร์ชันและการตรวจ | versions_to_keep, review_flow |
อย่างใดอย่างหนึ่งหรือทั้งสอง | expected_revisions.versions_to_keep, .review_flow |
| เมื่อไม่มีภาษา | policy (strict หรือ fallback_to_default), chains, languages |
policy, chains (แทนที่ทั้งหมด) |
expected_revisions.policy, .chains |
| ไฟล์สื่อ | kinds (แต่ละชนิดมี enabled, max_bytes, formats), limits (min_bytes, buffered_max_bytes, streamed_max_bytes, kind_max_bytes) |
kinds (แทนที่ทั้งหมด รูปภาพเปิดอยู่เสมอ ชนิดที่ไม่มี formats ใช้ค่าเริ่มต้น) |
expected_revision |
| การค้นหา | enabled, models, weights, modes และสิ่งที่เพิ่มหรือปรับได้ |
enabled, models, weights ที่ไม่บังคับ และ modes ที่ไม่บังคับ (word, english_stem หรือ substring ต่อ model ถ้าไม่ระบุคือ word และ english_stem ในภาษาที่ไม่ใช่ภาษาอังกฤษจับคู่ทั้งคำ) |
expected_revision |
| ค่าเริ่มต้นของ Site Chrome | footer.note ต่อภาษา, routes |
footer, routes |
expected_revision (เวอร์ชัน Site Chrome ที่ยอมรับแล้ว หรือ null) |
| SEO | ค่าเริ่มต้น seo, sitemap, robots, redirects |
ส่วนใดก็ได้ | expected_revision (เวอร์ชัน Site Chrome ที่ยอมรับแล้ว หรือ null) |
- revision ของการตั้งค่าที่ล้าสมัยได้
409 settings_changed_concurrentlyพร้อมfieldการเปลี่ยนที่ระบุการตั้งค่าโดยไม่มี revision ของมันได้400 invalid_request - การเก็บเวอร์ชันและขั้นตอนตรวจทาน การใช้ภาษาสำรองเมื่อไม่มีคำแปล ไฟล์สื่อ และการค้นหา เก็บคีย์ไว้กับการตั้งค่าในการเขียนครั้งเดียว การลองใหม่ด้วยคีย์และ body เดิมได้คำตอบแรก แม้ revision จะเปลี่ยนไปแล้ว การเขียนที่บันทึกแล้วแต่ยังไม่เสร็จได้
503 temporary_failureการลองใหม่จะทำให้เสร็จและส่งคำตอบเดิมกลับมา - ตัวตรวจตอบด้วยชื่อ เช่น
invalid_versions_to_keep,review_flow_items_in_progress(มีรายการรอตรวจ และแสดงรายการไว้),fallback_language_unknown,fallback_cycle,invalid_media_files,site_search_model_invalid,site_search_weights_invalidและsite_search_mode_invalid - ค่าเริ่มต้นของ Site Chrome และ SEO บันทึกเป็น Site Chrome เวอร์ชันใหม่ที่ยอมรับทันที เหมือนเมนู การเปลี่ยนแปลงที่ยังไม่ยอมรับจะถูกนำไปด้วย (
draft) และ revision ที่ล้าสมัยได้409 stale_document - รูปสำหรับการแชร์หรือโลโก้ระบุด้วย
og_image_media_id/logo_media_idจากคลังสื่อของไซต์นี้ เซิร์ฟเวอร์บันทึกที่อยู่และขนาดของรูปไว้ - การเปลี่ยนเส้นทางแทนที่ทั้งหมด ต้นทางแบบตรงตัวที่เป็นที่อยู่ที่เผยแพร่อยู่ ที่อยู่สงวนของบล็อก หรือ route ที่ตั้งชื่อไว้ จะถูกปฏิเสธ การเปลี่ยนกฎแบบ pattern ต้องมี
redirect_review_digest: ส่งกฎไปที่/settings/seo/redirect-reviewอ่านว่าแต่ละ pattern ครอบคลุมหน้าที่เผยแพร่อยู่หน้าใด แล้วส่งdigestของมัน หากไม่มีจะได้409 redirect_review_staleพร้อมผลการตรวจ - ไม่มีที่นี่: ความลับ รหัสผ่าน วิธีเข้าสู่ระบบ ผู้ให้บริการตัวตน ความลับและโทเค็นของปลั๊กอิน การ deploy ผู้ให้บริการซอร์ส โดเมน และโครงสร้างพื้นฐานอื่น
ความคิดเห็นของผู้เยี่ยมชม
หัวข้อที่มีชื่อว่า “ความคิดเห็นของผู้เยี่ยมชม”การดำเนินการกับความคิดเห็นรัน route ความคิดเห็นของแอดมินด้วยสิทธิ์ที่ถูกจำกัดของข้อมูลรับรอง กฎ การปฏิเสธ และคำตอบเรื่องความขัดแย้งจึงเหมือนของแอดมิน (ดู ความคิดเห็นของผู้เยี่ยมชม) การเขียนทุกครั้งต้องมี Idempotency-Key และบันทึกในประวัติเป็น management.comment.moderate, management.comment.delete หรือ management.comment.settings: จำนวนและสถานะเป้าหมาย ไม่ใช่ข้อความหรืออีเมลของความคิดเห็น
| การดำเนินการ | Scope | ทำอะไร |
|---|---|---|
GET /comments/settings (getCommentSettings) |
settings:write |
ทุกโมเดลและว่ารายการของโมเดลรับความคิดเห็นหรือไม่ |
PUT /comments/settings (updateCommentSettings) |
settings:write |
{ model, enabled }: เปิดหรือปิดหนึ่งโมเดล การปิดซ่อนความคิดเห็น ไม่มีอะไรถูกลบ |
GET /comments (listComments) |
lifecycle:publish |
ใหม่สุดก่อน หน้าละ 50 รายการ state (ค่าเริ่มต้น pending), model, entry, lang, cursor ไม่มีอีเมล |
GET /comments/{id} (getComment) |
lifecycle:publish |
ความคิดเห็นหนึ่งรายการพร้อมอีเมลส่วนตัว parent และรายการ |
POST /comments/moderate (moderateComments) |
lifecycle:publish |
{ to, comments: [{ id, expected }] } ไม่เกิน 25 รายการ แต่ละรายการเฉพาะเมื่อยังอยู่ในสถานะที่อ่านไว้ |
POST /comments/publish-imported (publishImportedComment) |
lifecycle:publish |
เผยแพร่ความคิดเห็นที่นำเข้าหนึ่งรายการโดยไม่มีความยินยอม ความยินยอมยังเป็น false |
POST /comments/remove (deleteComments) |
content:purge |
ลบความคิดเห็นไม่เกิน 25 รายการพร้อมคำตอบและ reaction ต้องมี lifecycle:publish และสิทธิ์ลบในโมเดลของแต่ละความคิดเห็นด้วย |
- ความคิดเห็นที่เปลี่ยนสถานะหลังจากอ่านข้อมูลจะตอบ
conflictพร้อมสถานะปัจจุบัน ความคิดเห็นที่นำเข้าโดยไม่มีความยินยอมจะตอบconsent_missingเมื่อถูกอนุมัติ - reaction เป็นของฝั่งผู้เยี่ยมชมและไม่มีการเขียนผ่าน management ความคิดเห็นในรายการมีจำนวน reaction ของตน
ความขัดแย้ง
หัวข้อที่มีชื่อว่า “ความขัดแย้ง”ทุกการเขียนระบุสถานะที่ใช้เป็นฐาน ค่าที่ล้าสมัยจะถูกปฏิเสธและไม่มีการเขียนใด ๆ
- แก้ไขรายการ:
expected_current_version_idหากล้าสมัย:409 version_conflictพร้อมcurrent_version_idการสร้างหรือแก้ไขระบุที่อยู่ในslugsหนึ่งค่าต่อหนึ่งภาษา ({ "fr": "bonjour" }) ภาษาที่ไม่ได้ระบุจะคงที่อยู่เดิม ที่อยู่ที่ถูกปฏิเสธจะระบุfield: "slug"และlocaleของมัน การสร้างในภาษาที่เว็บไซต์ไม่มีจะถูกปฏิเสธ400 unknown_localeพร้อมlangภาษาที่ถูกนำออกระหว่างการสร้างให้ผล409 language_changedและไม่มีสิ่งใดถูกเขียน - เผยแพร่:
to,expected_workflow_revision,expected_current_version_idฉบับร่างที่ใหม่กว่าจะไม่ถูกเผยแพร่โดยไม่มีใครรู้:409 scheduled_target_staleworkflow ที่ขยับไปแล้ว:409 workflow_conflictพร้อมrevision - ยกเลิกการเผยแพร่:
to,expected_workflow_revision,expected_published_version_idและacknowledged_dependentsที่เท่ากับidsจาก/entries/{id}/dependentsเมื่อมีเนื้อหาอื่นอ้างอิงรายการนี้ - ภาษาเดียว: เพิ่ม
localeในการเผยแพร่หรือยกเลิกการเผยแพร่เพื่อเคลื่อนย้ายเฉพาะภาษานั้นtoถูกตรวจกับ state ของภาษานั้นเองในworkflow.localesและการยกเลิกการเผยแพร่จะ pin เวอร์ชันที่เผยแพร่ของภาษานั้นเอง หากไม่มีlocaleทุกภาษาจะถูกเคลื่อนย้าย - บันทึกหน้า:
expected_current_version_idและexpected_facts_digestจากGET /pages/{id}การบันทึกหนึ่งครั้งมีหนึ่งภาษา (locale) และเปลี่ยนเฉพาะภาษานั้น แต่ละภาษามี section ของตัวเอง - เริ่มภาษาของหน้า:
locale(ภาษาที่ยังไม่มีในหน้านี้ หรือภาษาของไซต์ที่ยังไม่มีเวอร์ชันของหน้านี้),source(อีกภาษาหนึ่งของหน้าที่จะคัดลอก หรือnullสำหรับหน้าว่าง) และexpected_current_version_idการคัดลอกคงลำดับ รูปลักษณ์ ค่า และลิงก์ไปยัง shared section ไว้ภายใต้ id ของ section ใหม่409 translation_exists,422 translation_source_unavailable - ข้อเสนอ:
base_version_idต้องเป็นเวอร์ชันปัจจุบันของรายการ - lease การแก้ไข: ขณะที่บุคคลอื่นเปิดรายการและภาษาไว้ในตัวแก้ไข การบันทึก การย้าย การมอบหมาย การกู้คืน หรือถังขยะของรายการและภาษานั้นจะถูกปฏิเสธ
423 lockedพร้อมholderGET /entries/{id}/editing?locale=&kind=อ่านว่าใครถืออยู่และ draft ร่วม (kind:contentสำหรับตัวแก้ไขรายการpageสำหรับ page composer)POST /entries/{id}/editing/takeoverจบ lease นั้นทันที (ตัวแก้ไขเก็บงานไว้เป็น draft กู้คืน) และหลังจากนั้นไม่มีใครถือ ตัวแก้ไขที่คุณเปิดเองไม่เคยบล็อกข้อมูลรับรองของคุณ lease ที่หมดอายุไม่บล็อกเลย - draft ร่วม:
PUT /entries/{id}/editing/draftระบุkindและexpected_revision(0เมื่อยังไม่มี) ค่าที่ล้าสมัย:409 draft_conflictพร้อมcurrentPOST /entries/{id}/editing/draft/discardลบ draft นั้น draft ไม่ใช่เวอร์ชัน การอัปเดตรายการล้าง draft - เลื่อนขั้นตอน:
toและexpected_workflow_revisionพร้อมnoteและlocaleที่ไม่บังคับ ขั้นตอนที่จะเผยแพร่หรือยกเลิกการเผยแพร่ให้ผล422 transition_kind_mismatchให้ใช้การเผยแพร่หรือยกเลิกการเผยแพร่แทน การเลื่อนขั้นตอนโดยไม่มีlocaleระหว่างสองขั้นที่ไม่ได้เผยแพร่จะคงภาษาที่เผยแพร่อยู่แล้วไว้ที่เดิม ให้ยกเลิกการเผยแพร่ภาษานั้นแยกต่างหาก การเลื่อนขั้นตอนล้างการมอบหมาย เหมือนในหน้าผู้ดูแล - การมอบหมาย:
expected_workflow_revision,assignee_id(คนจาก/assigneesหรือnullเพื่อล้าง) และdue_dateที่ไม่บังคับ (YYYY-MM-DDวันนี้หรือหลังจากนั้น) ค่าเก่า:409 workflow_conflictพร้อมrevisionคนที่ดำเนินการกับรายการไม่ได้:422 assignee_cannot_act - กู้คืนเวอร์ชัน:
expected_current_version_idเวอร์ชันเก่ากลายเป็นฉบับร่างปัจจุบันใหม่ สิ่งที่เผยแพร่อยู่ไม่เปลี่ยน ค่าเก่า:409 version_conflictlocaleที่ไม่บังคับกู้คืนเฉพาะภาษานั้น: ฟิลด์ รายการความสัมพันธ์ของภาษานั้นเอง และที่อยู่มาจากเวอร์ชัน ฟิลด์ร่วม ความสัมพันธ์ร่วม สื่อ และทุกภาษาอื่นคงเดิม ภาษาที่เวอร์ชันหรือรายการไม่มี:422 restore_locale_missingต้องมีrestoreสำหรับภาษานั้น - ภาษาใหม่:
langจาก/languagesและslugที่ไม่บังคับ (null: ไม่มีที่อยู่ ถ้าไม่ส่ง: ใช้ที่อยู่ของภาษาหลัก และเติมส่วนท้ายถ้าภาษานี้มีที่อยู่นั้นอยู่แล้ว) ภาษาเริ่มว่าง การแก้ไขรายการครั้งถัดไปจะเขียนลงไป มีอยู่แล้ว:409 locale_existsพร้อมinstance_idของภาษานั้น - ถังขยะ:
scope: entryย้ายทุกภาษาscope: languageย้ายภาษาเดียวที่ไม่ใช่ภาษาหลัก (owner_languageสำหรับภาษาหลัก) เนื้อหาที่เผยแพร่อยู่ถูกปฏิเสธด้วย409 content_liveและlive_languagesให้ยกเลิกการเผยแพร่ก่อน รายการที่กู้คืนเป็นฉบับร่าง - กู้คืนจากถังขยะ:
GET /entries/{id}/trashระบุconflicts(ขวางการกู้คืน) และreferences(ลิงก์ที่จะใช้ไม่ได้) การกู้คืนถูกปฏิเสธด้วย409 restore_conflictขณะที่รายการอื่นใช้ที่อยู่นั้น - ลบถาวร:
confirmซ้ำ id ของรายการจากพาธ ลบรายการ ประวัติ และความคิดเห็นของผู้เยี่ยมชม ย้อนกลับไม่ได้GET /entries/{id}/trashบอกจำนวนความคิดเห็นที่จะลบในcommentsและคำตอบบอกจำนวนที่ลบแล้วในcomments_deleted - เชื่อมโยง (relate):
ids(1–25 รายการ),field(ฟิลด์ความสัมพันธ์ many-to-many ที่ทุกรายการมี),connectและ/หรือdisconnect(รายการที่ลิงก์รวม 1–10 รายการ ไม่มีรายการใดอยู่ทั้งสองฝั่ง) และexpected_versionsที่ระบุเวอร์ชันปัจจุบันของทุกรายการ แต่ละรายการเป็นการบันทึกฉบับร่างของตัวเองและได้ผลของตัวเอง:done,unchanged(เป็นอย่างที่ขออยู่แล้ว ไม่มีการเขียน),covered(ภาษาอื่นของรายการเดียวกันทำไปแล้ว สำหรับฟิลด์ที่มีลิงก์ต่างกันในแต่ละภาษา ภาษาแรกที่เลือกจะเปลี่ยนรายการของทุกภาษาที่เลือกในการบันทึกเดียว) หรือrefusedพร้อมreasonเช่นversion_conflictหรือbatch_relation_exclusiveสำหรับฟิลด์ one-to-many การปฏิเสธไม่หยุดรายการอื่น ไม่มีการเผยแพร่ - เชื่อมโยงจาก:
GET /entries/{id}/related-fromแสดงทุกฟิลด์ความสัมพันธ์ทุกชนิด (one-to-one, many-to-one, one-to-many, many-to-many) ที่มีเป้าหมายเป็นโมเดลของรายการนี้ เป็นgroupsแยกตามโมเดลและฟิลด์ต้นทาง พร้อมrelation_typeต้นทางแต่ละรายการมีlabelที่ใช้แสดง,draft/live,version_id,can_changeและreadableต้นทางที่ข้อมูลรับรองอ่านไม่ได้จะไม่มีslugและlabelPUTรับfield_id,source_id,action(addหรือremove) และexpected_source_version_idทั้งสองต้องมีreadบนรายการนี้ การเปลี่ยนต้องมีupdateบนต้นทางด้วย เป็นการบันทึกฉบับร่างของต้นทางเอง: ใช้สิทธิ์ กฎของฟิลด์ จำนวนความสัมพันธ์ กฎเจ้าของรายเดียว กฎลำดับชั้น และการตรวจเวอร์ชันของต้นทาง และไม่เผยแพร่เลย ค่าเก่า:409 version_conflictต้นทางแบบ to-one ที่ชี้ไปรายการอื่นอยู่แล้วจะถูกปฏิเสธด้วย409 related_from_replace_unconfirmedพร้อมระบุcurrentและnext(instance_id,slug,labelชื่อที่ข้อมูลรับรองอ่านไม่ได้เป็น null) ส่งreplace_target_idเป็นcurrent.instance_idเพื่อยืนยันการแทนที่นั้นโดยเฉพาะ คำตอบคือoutcome(savedหรือunchanged),version_idใหม่ของต้นทาง และrelated_fromที่อัปเดตแล้ว
GET /entries/{id} คืน state ของ workflow, revision และ transitions ที่ข้อมูลรับรองนี้ใช้ได้: kind: publish | unpublish เมื่อมี lifecycle:publish และ kind: move เมื่อมี workflow:move ค่า to ต้องเป็นหนึ่งในนั้น การเผยแพร่และการยกเลิกการเผยแพร่ปฏิเสธขั้นตอนชนิดอื่นด้วย transition_kind_mismatch และการเลื่อนขั้นตอนก็เช่นกัน
ข้อเสนอให้บรรณาธิการตรวจ
หัวข้อที่มีชื่อว่า “ข้อเสนอให้บรรณาธิการตรวจ”การเชื่อมต่อที่ไม่ควรเขียนโดยตรงสามารถเสนอการเปลี่ยนฟิลด์แทนได้:
{ "base_version_id": "<current version id>", "fields": { "title": "A clearer title" }, "note": "Why"}- ฟิลด์ค่าของโมเดลรายการได้สูงสุด 50 ฟิลด์ ค่ารวมไม่เกิน 64 KiB และหมายเหตุไม่เกิน 2,000 อักขระ ฟิลด์ความสัมพันธ์และฟิลด์สื่อเสนอไม่ได้ (
proposal_field_unsupported) - ไม่มีอะไรเปลี่ยนจนกว่าจะมีคนนำไปใช้ ตัวแก้ไขรายการแสดงข้อเสนอที่เปิดอยู่เหนือฟิลด์ และแสดงค่าที่เสนอข้างฟิลด์ที่ระบุ พร้อมปุ่ม ใช้ และ ปฏิเสธ
- ใช้ คือการบันทึกปกติในนามผู้ที่กดใช้ และผูกกับ
base_version_idการตรวจสอบ สิทธิ์ กฎความเป็นเจ้าของ และ workflow ของผู้นั้นมีผลทั้งหมด หากรายการเปลี่ยนไปหลังจากมีข้อเสนอ ใช้ จะปฏิเสธด้วยproposal_staleและไม่เขียนอะไร - ตัวแก้ไขจะปิด ใช้ ขณะมีการแก้ไขที่ยังไม่บันทึก ปฏิเสธ จะปล่อยรายการไว้ตามเดิม
sourceของข้อเสนอระบุผู้สร้าง:{ "kind": "management", "id", "label" }สำหรับ credential หรือ{ "kind": "plugin", "id", "label", "requested_by" }สำหรับการติดตั้ง plugin ที่ทำแทน บุคคล (docs/plugins/protocol-v1.md) ตัวแก้ไขแสดงข้อเสนอจาก plugin เป็นProposed by <plugin> · requested by <person>รายการข้อเสนอยังรวม{ "kind": "ai", "id", "label" }โดยidระบุงาน AI แบบกลุ่ม ข้อเสนอแต่ละรายการมีrevisionส่วนoriginal_fieldsเป็นฟิลด์เสริมที่มีค่าเดิมของฟิลด์ที่เสนอ เฉพาะค่าที่ผู้ใช้มีสิทธิ์อ่าน
การเปลี่ยนโมเดลโดยตรง
หัวข้อที่มีชื่อว่า “การเปลี่ยนโมเดลโดยตรง”schema:write เปลี่ยนโมเดลโดยไม่มีขั้นตอนตรวจ ทุกการเขียนใช้การ apply หรือการลบของ ตัวสร้างเนื้อหา เอง: การตรวจ การปฏิเสธการเปลี่ยนแปลงที่ไม่เข้ากัน กฎความสัมพันธ์ การย้ายสิทธิ์ และบันทึกตรวจสอบชุดเดียวกัน
GET /models/{id}ตอบdefinitionทั้งหมดและrevision- เขียนโดยอ้าง
revisionนั้น:
-
POST /modelsพร้อม{ "definition": { "model": …, "fields": [ … ] } }สร้างโมเดล ไม่มี id ของฟิลด์ -
PUT /models/{id}พร้อมexpected_revisionและmodel(name,slug,description,settings,enableI18n) เปลี่ยนโมเดลและคงฟิลด์ไว้ -
PUT /models/{id}/folderพร้อม{ "folder": "Blog" }(1 ถึง 40 ตัวอักษร หรือnull) ใส่โมเดลในโฟลเดอร์นำทางเนื้อหาหรือเอาออก เป็นการแสดงผลเท่านั้น: ไม่มีexpected_revision,revisionของโมเดลไม่เปลี่ยน และlistModelsแสดงเป็นfolderคำตอบคือmodel_id,slugและfolder -
PUT /models/{id}/definitionพร้อมexpected_revisionและdefinitionที่ครบถ้วน แทนที่นิยามเดิม: คงฟิลด์ไว้ด้วยidเพิ่มฟิลด์โดยไม่มี id และเอาฟิลด์ออกเพื่อลบ -
POST /models/{id}/removeพร้อมexpected_revisionและconfirm(id ของโมเดลอีกครั้ง) ลบโมเดลที่ไม่มีเนื้อหา -
คำตอบคือ
model_id,slug,created_field_idsและrenamed_fromหลังการเปลี่ยนชื่อหรือการลบdependents.completeบอกว่าสิทธิ์ของบทบาทและขอบเขตโทเค็นทุกอันที่ระบุ slug เดิมถูกย้ายหรือลบแล้วหรือไม่ เมื่อเป็นการเล่นซ้ำจะไม่มีฟิลด์นี้ -
revisionเปลี่ยนเมื่อโมเดลหรือฟิลด์ใดเปลี่ยน revision ที่ล้าสมัยได้409 model_changedและไม่เขียนอะไรเลย -
การเปลี่ยนที่ไม่ปลอดภัยต่อเนื้อหาที่มีอยู่จะถูกปฏิเสธด้วย
409 model_change_blockedและchanges(kind,field,affected_count,message,suggestion) ไม่มีการข้าม ให้ย้ายหรือล้างเนื้อหาก่อน -
การปฏิเสธอื่นใช้เหตุผลเดียวกับ ตัวสร้างเนื้อหา เช่น
duplicate_field_key,field_not_found,unique_unsupported,model_name_collision,stale_role_grant,stale_token_scope,model_not_emptyและmodel_has_bound_queries -
ผู้สร้างต้องมี
model:applyในขณะนั้น และสำหรับการลบต้องมีmodel:deleteด้วย ถ้าไม่มีจะได้403 missing_permission
ข้อเสนอ schema
หัวข้อที่มีชื่อว่า “ข้อเสนอ schema”แทนที่จะเปลี่ยนโมเดล เอเจนต์สามารถเสนอโมเดลใหม่หรือนิยามที่แก้ไขแล้ว ผู้ใช้ตรวจใน ตัวสร้างเนื้อหา และนำไปใช้ผ่านขั้นตอนบันทึก ตรวจการเปลี่ยนแปลงที่ไม่เข้ากัน และนำโมเดลไปใช้ตามปกติ
GET /models/{id}ตอบdefinitionทั้งหมดของโมเดล (modelและfieldsโดยแต่ละฟิลด์มีid),updated_atและrevisionPOST /models/proposalsพร้อมนิยามที่แก้ไขแล้ว:
{ "mode": "update", "model_id": "<model id>", "base_updated_at": "<updated_at from getModel>", "definition": { "model": { "name": "Article", "slug": "article", "enableI18n": true }, "fields": ["…"] }, "note": "Why"}definitionต้องครบถ้วน: คงแต่ละฟิลด์ไว้ด้วยidของมัน เพิ่มฟิลด์โดยไม่มีidและไม่ใส่ฟิลด์ที่ต้องการลบ ลำดับฟิลด์คือลำดับในอาร์เรย์config,derived,uiและvalidationเป็นข้อความ JSON ตามที่ ตัวสร้างเนื้อหา เก็บไว้- โมเดลใหม่คือ
{ "mode": "create", "definition": { … }, "note": … }โดยไม่มี id ของฟิลด์ - ไม่เกิน 200 ฟิลด์ และ note ไม่เกิน 500 ตัวอักษร
- นิยามถูกอ่านและตรวจแบบเดียวกับที่การ apply อ่าน สิ่งที่การ apply ปฏิเสธจะถูกปฏิเสธที่นี่ด้วยเหตุผลเดียวกัน เช่น
duplicate_field_key,field_not_found,unique_unsupported,model_slug_takenและmodel_name_collisionหากโมเดลเปลี่ยนไปหลังgetModelคำตอบคือ409 model_changedพร้อมcurrent_updated_at - การเปลี่ยนที่จะทำให้เนื้อหาเดิมเสียหายทำได้ และจะแสดงใน
changes(kind,field,affected_count,message,suggestion) ผู้ใช้จะเห็นการตรวจเดียวกันอีกครั้งก่อน apply - ผู้สร้างต้องมี
model:applyในขณะนั้น หากไม่มี คำตอบคือ403 missing_permission - ตัวสร้างเนื้อหา → โมเดล แสดงข้อเสนอที่เปิดอยู่พร้อม ตรวจทาน และ ปัดตก ตรวจทาน เปิดเอดิเตอร์พร้อมนิยามที่เสนอ บันทึก จะนำไปใช้ เป็นการเปลี่ยนโมเดลตามปกติในนามผู้ที่บันทึก และทำเครื่องหมายข้อเสนอเป็น
appliedในการเขียนเดียวกัน ผู้ใช้แก้ไขก่อนได้ - ข้อเสนอผูกกับ
base_updated_atเมื่อโมเดลเปลี่ยน ข้อเสนอจะเป็นstale: trueการ apply จะถูกปฏิเสธด้วย409 schema_proposal_staleและทำได้เพียงปัดตกข้อเสนอ GET /models/proposalsแสดงข้อเสนอของไซต์ ใหม่สุดก่อน (stateกรองopen,applied,dismissed)GET /models/proposals/{id}อ่านข้อเสนอหนึ่งรายการ
การเปิดปลั๊กอิน
หัวข้อที่มีชื่อว่า “การเปิดปลั๊กอิน”เอเจนต์เปิดปลั๊กอินที่ติดตั้งไว้ให้ผู้ใช้ได้ การเปิดไม่เคยสั่งให้ปลั๊กอินทำงาน การดำเนินการ ที่ประกาศไว้เป็นสิ่งที่สั่งได้
GET /pluginsแสดงปลั๊กอินที่ติดตั้ง:state(activeหรือdisabled),launchableและสำหรับปลั๊กอินที่ใช้งานอยู่คือmanage_viewsและcontent_actionsข้อมูลมาจาก manifest ที่บันทึกไว้ตอนติดตั้งหรืออัปเกรดGET /plugins/manage-viewsและGET /plugins/content-actionsแสดงรายการเดียวกันแบบรายการเรียบ เมื่อระบุinstance_idจะแสดงเฉพาะการทำงานที่ใช้ได้กับรายการนั้น- การทำงานกับรายการแสดง
model_ids: โมเดลที่ให้สิทธิ์ปลั๊กอินและข้อมูลรับรองนี้อ่านได้ GET /rich-blocks(content:read) แสดงนิยามบล็อก rich text ของแต่ละปลั๊กอินที่ติดตั้ง: ฟิลด์ กฎ ค่าตัวอย่าง render tree และ migration ในรูป<plugin id>/<name>เขียนบล็อกลงในค่า rich เป็น figure แบบ canonical ของบล็อก (ดู Blog → บล็อกของปลั๊กอิน) เพิ่มหรือเปลี่ยนได้เฉพาะบล็อกของปลั๊กอินที่ใช้งานอยู่- แสดงเฉพาะการเปิดที่ข้อมูลรับรองเปิดได้ตอนนี้ ทั้งในรายการและในเครื่องมือ หน้าของปลั๊กอินต้องมีโมเดลที่ให้สิทธิ์ปลั๊กอินซึ่งผู้สร้างอ่านได้ รวมถึงอ่านได้เฉพาะรายการของตัวเอง และอยู่ในโมเดลของข้อมูลรับรอง การทำงานกับรายการต้องมีโมเดลอย่างน้อยหนึ่งโมเดลใน
model_idsการเปลี่ยนสิทธิ์มีผลตั้งแต่คำขอถัดไป การเปิดที่ส่งมาทั้งที่ไม่แสดงก็ยังถูกตรวจครบทุกข้อ POST /plugins/{id}/manage-views/{view}/launchและPOST /plugins/{id}/content-actions/{action}/entries/{instanceId}/launchสร้างลิงก์เปิดหนึ่งลิงก์ คำตอบคือ201พร้อมoutcome: launch_created,url,expires_atและsingle_use: true- ลิงก์มีรหัสเปิดหนึ่งรหัส ใช้ได้ครั้งเดียวและหมดอายุ 60 วินาทีหลังสร้าง ผู้ใช้เปิดลิงก์ในเบราว์เซอร์ แล้วปลั๊กอินทำงานในนามผู้สร้างข้อมูลรับรอง เหมือนตอนที่ผู้ใช้คนนั้นเปิดปลั๊กอินจากหน้าแอดมิน ก่อนหน้านั้นปลั๊กอินไม่ได้รับอะไรเลย และคำตอบไม่เคยบอกว่าปลั๊กอินทำอะไรแล้ว
- เมื่อเปิดแล้ว ปลั๊กอินทำงานภายในสิทธิ์ของตัวเองและสิทธิ์ของผู้สร้าง จำกัดด้วยโมเดลของข้อมูลรับรองและถูกตัดด้วย scope delegation มีเฉพาะ capability ของปลั๊กอินที่ scope อนุญาต:
content:readให้content:readและworkflow:read;lifecycle:publishให้workflow:publish,workflow:unpublishและworkflow:locale;content:writeให้content:write,content:createและdraft:write;content:trashให้content:delete;media:writeให้media:write,media:read,media:updateและmedia:source;content:proposeให้ชื่อเดียวกัน คำสั่งของปลั๊กอินที่อยู่นอกนั้นคือcapability_refused - ทุกครั้งที่เปิดจะตรวจสิทธิ์ปัจจุบันของผู้สร้าง โมเดลของข้อมูลรับรอง และสิทธิ์ของปลั๊กอินอีกครั้ง เป็นการตรวจเดียวกับในหน้าแอดมิน ปลั๊กอินที่ปิดอยู่ได้
409 installation_not_activeปลั๊กอินที่ถอนการติดตั้งแล้วหรือของไซต์อื่นได้404 not_found - ไม่มี
Idempotency-Key: ทุกการเรียกสร้างลิงก์ใหม่ ลิงก์ที่ไม่ได้ใช้จะหมดอายุเอง
การดำเนินการของปลั๊กอิน
หัวข้อที่มีชื่อว่า “การดำเนินการของปลั๊กอิน”manifest ของปลั๊กอินประกาศการดำเนินการได้: ฟอร์มเล็ก ๆ และผลลัพธ์ที่ Dee Wan วาดเอง เอเจนต์เรียกใช้แล้วได้ผลลัพธ์กลับมา
GET /plugins/operationsแสดงการดำเนินการที่ข้อมูลรับรองนี้เรียกใช้ได้ตอนนี้:scope(siteหรือentry),destructive,input(ช่องที่ประกาศไว้),input_schema(JSON Schema ของinput),result(ส่วนประกอบของผลลัพธ์) และmodel_ids(สำหรับการดำเนินการกับรายการ คือโมเดลที่ใช้ได้) เมื่อระบุinstance_idจะแสดงเฉพาะการทำงานที่ใช้ได้กับรายการนั้นGET /pluginsมีรายการเดียวกันแยกตามปลั๊กอิน- การดำเนินการจะแสดงเฉพาะตอนที่ปลั๊กอินใช้งานอยู่และสิทธิ์ของปลั๊กอินมี capability ทุกอย่างที่การดำเนินการต้องใช้ ภายในสิ่งที่ scope ของข้อมูลรับรองอนุญาต การดำเนินการกับรายการยังต้องมีโมเดลที่ให้สิทธิ์ซึ่งผู้สร้างอ่านได้ภายในโมเดลของข้อมูลรับรอง ส่วนที่ทำลายข้อมูลต้องมี
updateการดำเนินการระดับไซต์ต้องมีsite:pluginsและปลั๊กอินจะกระทำแทนผู้สร้างบนโมเดลที่ได้รับสิทธิ์ซึ่งผู้สร้างอ่านได้ ภายในโมเดลของข้อมูลรับรอง และจำกัดตาม scopereleases,release-editและrelease-runของ Scheduling เป็นการดำเนินการแบบนี้ การเปลี่ยนสิทธิ์มีผลตั้งแต่คำขอถัดไป POST /plugins/{id}/operations/{operation}/invokeเรียกใช้ body คือ{ "instance_id"?: "…", "input"?: { … } }โดยinstance_idจำเป็นสำหรับการดำเนินการกับรายการและถูกปฏิเสธสำหรับระดับไซต์inputเป็นแบบปิด: คีย์ที่ไม่รู้จักหรือค่านอกขอบเขตของช่องได้400 input_invalidพร้อมissues- คำตอบคือ
200พร้อมoutcome: completedและresult.blocks:text,rows,links(https เท่านั้น) และjsonIdempotency-Keyและ body เดิมจะคืนผลลัพธ์แรกพร้อมreplayed: trueส่วนคีย์เดิมกับ body อื่นได้409 idempotency_key_reusedการเล่นซ้ำจะถูกตรวจเหมือนการเรียกใหม่ก่อน ถ้าข้อมูลรับรองนี้เรียกการดำเนินการนั้นไม่ได้แล้ว จะได้404 operation_unavailableไม่ใช่ผลลัพธ์เดิม - การปฏิเสธ:
404 operation_unavailable(ไม่รู้จัก หรือข้อมูลรับรองนี้ใช้ไม่ได้ตอนนี้),422 plugin_refused(refusal: { code, message }ของปลั๊กอินเอง),504 plugin_timeout,502 plugin_unreachable,502 plugin_response_invalid,409 plugin_unavailable(ปลั๊กอินถูกปิด ลดสิทธิ์ หรือเปลี่ยนระหว่างทำงาน),409 invocation_in_progress - ปลั๊กอินทำงานในนามผู้สร้างข้อมูลรับรอง และไม่เคยได้รับข้อมูลรับรอง: core ส่งรหัสใช้ครั้งเดียว แล้วปลั๊กอินแลกรหัสนั้นด้วยโทเค็นการติดตั้งของตัวเอง การเรียกใช้ถูกบันทึกในการตรวจสอบเป็น
plugin.operation.runพร้อม digest ของค่าและผลลัพธ์ ไม่ใช่ตัวค่าเอง
นโยบายเนื้อหาของปลั๊กอิน
หัวข้อที่มีชื่อว่า “นโยบายเนื้อหาของปลั๊กอิน”ปลั๊กอินอาจประกาศนโยบายเนื้อหา: การตรวจที่ Dee Wan ถามก่อนการบันทึก การกู้คืน การลบ การเผยแพร่ การยกเลิกการเผยแพร่ หรือการย้าย workflow การอัปเดตรายการ การบันทึกหน้า การกู้คืนเวอร์ชัน คำสั่งถังขยะ และการเปลี่ยนสถานะผ่าน API นี้จะเรียกการตรวจแบบเดียวกับในตัวแก้ไข โดยมี caller.via: management_credential GET /plugins แสดง policies ของแต่ละปลั๊กอินพร้อม kind และ mode
- คำตอบมี
policy_results: นโยบายที่เตือน นโยบายในโหมด warn ที่ไม่ตอบ (outcome: unavailableพร้อมfailure) หรือนโยบายที่แปลงเนื้อหาที่บันทึก (outcome: transformedโดยfieldsระบุสิ่งที่เปลี่ยน) แต่ละรายการระบุinstallation_id,plugin_name,policy_id,policy_label,locale,messageและfieldsการเขียนเสร็จแล้ว อย่าส่งซ้ำ หลังการแปลง ให้อ่านรายการอีกครั้งเพื่อดูค่าที่เก็บ 422 plugin_policy_transform_invalid: เนื้อหาที่นโยบายแปลงละเมิดกฎcauseมีcode,messageและdetailของกฎ ไม่มีการเขียน422 plugin_policy_transform_conflict: สองนโยบายเปลี่ยนฟิลด์เดียวกันต่างกัน ไม่มีการเขียน422 plugin_policy_refused: นโยบายปฏิเสธ ระบุplugin,policy,policy_label,locale,codeและmessageของปลั๊กอินเอง และfieldsไม่มีอะไรถูกเขียน503 plugin_policy_unavailable: นโยบายในโหมดบล็อกไม่ตอบ ระบุนโยบายและfailureไม่มีอะไรถูกเขียน ลองใหม่ภายหลัง429 plugin_policy_budget_exhausted: คำขอเดียวถามนโยบายบ่อยเกินกว่าที่อนุญาต ส่งรายการให้น้อยลงต่อครั้ง
บรรทัดคำสั่งและ MCP
หัวข้อที่มีชื่อว่า “บรรทัดคำสั่งและ MCP”dee-wan-manage.mjs เป็นไฟล์เดียวที่ไม่มี dependency ต้องใช้ Node 18 ขึ้นไป ดาวน์โหลดได้จาก การตั้งค่า → API ข้อมูลรับรองคือ DEE_WAN_MANAGEMENT_TOKEN เมื่อตั้งค่าไว้ มิฉะนั้นจะใช้การเข้าสู่ระบบที่บันทึกไว้สำหรับ origin นั้น (ดู ลงชื่อเข้าใช้จากเทอร์มินัล) origin ของ CMS คือ --url หรือ DEE_WAN_URL origin ต้องใช้ https (ใช้ http ได้เฉพาะบน localhost, 127.0.0.1 หรือ [::1])
export DEE_WAN_MANAGEMENT_TOKEN=<credential>node dee-wan-manage.mjs helpnode dee-wan-manage.mjs list-entries --url https://cms.example --model-id <model id>node dee-wan-manage.mjs create-entry --url https://cms.example --body-file entry.jsonnode dee-wan-manage.mjs upload-media --url https://cms.example --file photo.webpnode dee-wan-manage.mjs launch-plugin-manage-view --url https://cms.example --id <installation id> --view calendar- แต่ละคำสั่งคือหนึ่งการดำเนินการในตารางด้านบน เขียนแบบ kebab case (
publishEntry→publish-entry) ไม่มีโหมดส่งคำขอดิบ - การเขียนส่ง
Idempotency-Keyเสมอ คีย์จะพิมพ์ออกทาง stderr ระบุคีย์เดิมด้วย--key <key>เพื่อลองใหม่อย่างปลอดภัย - รหัสออก
0เมื่อสำเร็จ1เมื่อ Dee Wan ปฏิเสธ (จะพิมพ์คำตอบออกมา) และ2สำหรับปัญหาการใช้งาน เครือข่าย หรือข้อมูลรับรอง
ลงชื่อเข้าใช้จากเทอร์มินัล
หัวข้อที่มีชื่อว่า “ลงชื่อเข้าใช้จากเทอร์มินัล”node dee-wan-manage.mjs login --url https://cms.examplenode dee-wan-manage.mjs status --url https://cms.examplenode dee-wan-manage.mjs logout --url https://cms.examplelogin ลงชื่อเข้าใช้ด้วยบัญชีของคุณเอง เหมือน เชื่อมต่อแอป:
- อ่าน
/.well-known/oauth-authorization-serverissuer ต้องเป็น origin นั้น และ endpoint ของ token, registration และ revocation ต้องอยู่บน origin นั้น ต้องรองรับ PKCES256 - เริ่ม listener บน
127.0.0.1เท่านั้น บนพอร์ตที่ระบบเลือก และลงทะเบียน client ที่มี redirect URI เดียวคือhttp://127.0.0.1:<port>/callback - เปิด
/connectในเบราว์เซอร์พร้อม PKCES256และstateแบบสุ่ม--no-openจะพิมพ์ที่อยู่ออกมาแทน--scope "content:read content:write"ขอ scope น้อยลง หากไม่ระบุจะขอทุก scope คุณเลือกไซต์ในหน้ายินยอม - callback ต้องเป็น
GET /callbackที่มีstateเดียวกัน มี issuer เป็นissและมีcodeหนึ่งค่าพอดี path อื่นตอบ404ยกเลิก จบการลงชื่อเข้าใช้ด้วยรหัสออก1หลังจาก 5 นาทีจะเลิกรอด้วยรหัสออก2 - แลกโค้ดและบันทึกการเข้าสู่ระบบ
การลงชื่อเข้าใช้ที่ล้มเหลวหรือถูกยกเลิกจะไม่เขียนอะไร การเข้าสู่ระบบที่บันทึกไว้จะคงเดิม
การเข้าสู่ระบบที่บันทึกไว้:
- หนึ่งรายการต่อ origin ในไดเรกทอรีการตั้งค่าของระบบปฏิบัติการ:
~/Library/Application Support/dee-wanบน macOS,$XDG_CONFIG_HOME/dee-wanหรือ~/.config/dee-wanบน Linux,%APPDATA%\dee-wanบน Windows ไดเรกทอรีเป็น0700และไฟล์เป็น0600(บน Windows ตั้งสิทธิ์ได้เท่าที่ระบบรองรับ) - เก็บ origin, client id, ไซต์, scope และ refresh token ปัจจุบัน ไม่เก็บ access token เลย ไม่มีคำสั่งใดพิมพ์ token
- ทุกคำสั่งต่ออายุมัน: ล็อก origin นั้น ใช้ refresh token เขียนตัวใหม่ลงไฟล์ชั่วคราวในไดเรกทอรีเดียวกัน flush แล้ว rename ทับไฟล์เดิม สองคำสั่งที่รันพร้อมกันจะรอกัน refresh token จึงไม่ถูกใช้สองครั้ง ล็อกที่ โพรเซสที่สิ้นสุดแล้วทิ้งไว้ หรือที่เก่ากว่า 2 นาที จะถูกล้าง
- เมื่อ Dee Wan ไม่รับมันอีกต่อไป (ถูกเพิกถอน หมดอายุหลังไม่ได้ใช้ 30 วัน หรือผู้ใช้เสียสิทธิ์ในไซต์) คำสั่งจะออกด้วยรหัส
2และขอให้รันloginอีกครั้ง ไฟล์จะถูกเก็บไว้ loginใหม่สำหรับ origin เดียวกันจะแทนที่มันและเพิกถอนการเชื่อมต่อเดิมDEE_WAN_MANAGEMENT_TOKENมีลำดับเหนือกว่าเสมอ และไม่เคยถูกบันทึกstatusบอกเมื่อมันแทนที่การเข้าสู่ระบบที่บันทึกไว้
status แสดง origin, ไซต์และ scope ที่บันทึกไว้ มี client id บันทึกไว้หรือไม่ และตั้ง DEE_WAN_MANAGEMENT_TOKEN ไว้หรือไม่ หากไม่มีทั้งสองอย่างจะออกด้วยรหัส 2 logout เพิกถอนการเชื่อมต่อที่ revocation endpoint ก่อน แล้วจึงลบไฟล์ หากการเพิกถอนล้มเหลว ไฟล์จะถูกเก็บไว้และออกด้วยรหัสที่ไม่ใช่ศูนย์ ให้รันอีกครั้ง
node dee-wan-manage.mjs mcp --url <origin> รันเซิร์ฟเวอร์ MCP ผ่าน stdio เครื่องมือของมันคือการดำเนินการชุดเดียวกัน เขียนแบบ snake case (publish_entry) พร้อม input schema จากสัญญา เหมือนเซิร์ฟเวอร์ระยะไกล มันแสดงเฉพาะเครื่องมือที่ขอบเขตของข้อมูลรับรองอนุญาต (อ่านจาก GET /site) และเครื่องมือของปลั๊กอินด้านล่าง เครื่องมือเขียนรับ idempotency_key แบบไม่บังคับและคืนคีย์ที่ใช้ การเรียกตอบเป็น structuredContent { status, idempotency_key?, body } และวัตถุเดียวกันเป็นข้อความ JSON การตั้งค่า → API แสดงการตั้งค่าไคลเอนต์ให้คัดลอก:
{ "mcpServers": { "dee-wan": { "command": "node", "args": [ "/path/to/dee-wan-manage.mjs", "mcp", "--url", "https://cms.example" ], "env": { "DEE_WAN_MANAGEMENT_TOKEN": "<your credential>" } } }}หากไม่มี env เซิร์ฟเวอร์ MCP จะใช้การเข้าสู่ระบบที่บันทึกไว้สำหรับ --url และต่ออายุภายใต้ล็อกเดียวกันขณะที่ session ทำงาน
เครื่องมืออัปโหลดอ่านพาธไฟล์ในเครื่องที่รันเซิร์ฟเวอร์
การนำเข้าและส่งออกจากบรรทัดคำสั่ง:
node dee-wan-manage.mjs create-import --body '{"format":"wxr","source":{"label":"Old blog","system":"wordpress"}}'node dee-wan-manage.mjs upload-import-part --id <job id> --file export.xmlnode dee-wan-manage.mjs advance-import --id <job id> --follownode dee-wan-manage.mjs create-export --body '{"product":"bundle"}'node dee-wan-manage.mjs advance-export --id <export id>node dee-wan-manage.mjs read-export-chunk --id <export id> --out site.jsonupload-import-part --fileส่งทั้งไฟล์เป็นส่วนละ 5 MiB แล้วจบการอัปโหลดด้วย SHA-256 ของไฟล์ คีย์ของแต่ละส่วนมาจากไบต์ของส่วนนั้น การรันซ้ำจึงได้ผลเดิม ส่วนไบต์ที่เปลี่ยนไปจะถูกปฏิเสธ--part <n>ส่งเพียงส่วนเดียว เครื่องมือ MCP แบบ stdio รับfile_pathและpartที่ไม่บังคับadvance-import --followเดินหน้าทีละขั้น พิมพ์หนึ่งบรรทัดต่อขั้นใน stderr และหยุดเมื่อต้องตัดสินใจ เมื่อจบ หรือเมื่อถูกปฏิเสธ สถานะสุดท้ายพิมพ์เป็น JSON ผ่าน MCP การเรียกแต่ละครั้งคือหนึ่งขั้นread-export-chunk --out <file>อ่านการส่งออกที่เสร็จแล้วทีละส่วน ตรวจ SHA-256 แล้วจึงเขียนไฟล์ เมื่อใช้--page <n>จะบันทึกหน้าที่เก็บไว้หนึ่งหน้า--finishเดินหน้าการส่งออกจนเสร็จก่อน แต่ละขั้นใช้คีย์ที่ได้จากการส่งออกและขั้นนั้น การรันอีกครั้งหลังคำตอบหายจึงเล่นซ้ำแทนการสร้างสองครั้งread-import-artifact --out <file>บันทึก artifact การนำเข้าทั้งไฟล์ด้วยวิธีเดียวกันget-import-findings,get-import-mapping,get-word-press-review,get-history-assignmentsและget-import-principals --allอ่านทุกหน้ารวมเป็นคำตอบเดียว- เมื่อได้
429และ409 idempotency_in_progressพร้อมRetry-Afterคำสั่งจะรอตามRetry-Afterแล้วลองใหม่ด้วยคีย์เดิม ถ้าไม่มีRetry-Afterคำสั่งจะหยุดและพิมพ์คำตอบ
MCP ระยะไกล
หัวข้อที่มีชื่อว่า “MCP ระยะไกล”เอเจนต์ที่เชื่อมต่อผ่าน HTTPS ไม่ต้องดาวน์โหลด ให้เอเจนต์เพียงที่อยู่ แล้วเอเจนต์จะพาคุณลงชื่อเข้าใช้ (ดู เชื่อมต่อแอป):
https://cms.example/api/manage/mcpสำหรับงานอัตโนมัติ ให้ส่งข้อมูลรับรองการจัดการแทน การตั้งค่า → API แสดงทั้งสองแบบ:
{ "mcpServers": { "dee-wan": { "type": "http", "url": "https://cms.example/api/manage/mcp", "headers": { "Authorization": "Bearer <your credential>" } } }}- ข้อความ JSON-RPC หนึ่งข้อความต่อ
POSTและคำตอบ JSON หนึ่งคำตอบ ไม่มีเซสชันและไม่มีสตรีม:GETและDELETEให้405การแจ้งเตือนได้202แบบกลุ่ม (batch) ถูกปฏิเสธ - เวอร์ชันโปรโตคอล
2025-06-18และ2025-03-26ส่วนหัวMCP-Protocol-Versionอื่นให้400 - ข้อมูลรับรองถูกอ่านใหม่ทุกข้อความ ข้อมูลรับรองที่ถูกเพิกถอนได้
401ในข้อความถัดไป แต่ละPOSTนับในขีดจำกัดอัตราหนึ่งครั้ง tools/listแสดงเฉพาะเครื่องมือที่ขอบเขตของข้อมูลรับรองอนุญาต การเรียกผ่านการตรวจขอบเขต บันทึกตรวจสอบ ความขัดแย้ง และ idempotency เดียวกับ HTTP API ในนามผู้สร้างข้อมูลรับรอง- คำขอจากหน้าเบราว์เซอร์ของ origin อื่นได้
403 - เครื่องมืออัปโหลดระยะไกลรับไฟล์เป็น
content_base64และfile_nameไม่เกิน 8 MiB หลังถอดรหัส upload_import_partรับหนึ่งส่วนเป็นcontent_base64พร้อมidและpartส่วนread_export_chunkและread_import_artifactตอบหนึ่งช่วงไบต์เป็น{ content_base64, content_type, offset, length, total, next_offset, sha256, page, next_page }ไม่เคยตอบทั้งไฟล์ขนาดใหญ่content_typeและsha256อธิบายทั้งไฟล์ และpageและnext_pageเป็นnullเมื่อไม่ใช่หน้าของการส่งออกget_import_findingsอ่านผลการตรวจหนึ่งหน้า- เมื่อมี
plugins:useหน้าและการทำงานกับรายการของปลั๊กอินที่ใช้งานอยู่แต่ละอย่างจะเป็นเครื่องมือด้วย เช่นplugin_com_example_scheduler_view_calendar(ไม่มีอาร์กิวเมนต์) หรือplugin_com_example_scheduler_action_schedule(instance_id) ชื่อคือplugin_ตามด้วย id ของปลั๊กอินviewหรือactionและ id ของมัน เป็นตัวพิมพ์เล็ก อักขระอื่นแทนด้วย_เครื่องมือถูกอ่านใหม่ทุกข้อความ ปลั๊กอินที่ปิดหรือถอนการติดตั้งจะไม่มีเครื่องมือตั้งแต่ข้อความถัดไป ชื่อที่การเปิดสองอย่างใช้ร่วมกัน ชื่อที่ตรงกับเครื่องมือหลัก หรือชื่อยาวเกิน 64 อักขระ ระบบจะไม่แสดงเครื่องมือทั้งหมดที่มีชื่อชนกันหรือชื่อยาวเกินกำหนดGET /pluginsบอกเหตุผลในmcp_tool.omitted_becauseการเรียกหน้าหรือการทำงานเพียงสร้างลิงก์เปิดตามข้างต้น อะแดปเตอร์ stdio แสดงเครื่องมือชุดเดียวกัน - การดำเนินการแต่ละอย่างที่ข้อมูลรับรองเรียกใช้ได้ก็เป็นเครื่องมือด้วย เช่น
plugin_com_example_scheduler_op_statusอาร์กิวเมนต์คือinstance_idสำหรับการดำเนินการกับรายการinput(input_schemaของการดำเนินการ) และidempotency_keyที่ไม่บังคับ การเรียกจะเรียกใช้การดำเนินการทันทีและคืนผลลัพธ์เป็นstructuredContentและข้อความ annotation ยังคงdestructiveไว้ เครื่องมือที่ข้อมูลรับรองใช้ไม่ได้แล้วจะหายไปตั้งแต่ข้อความถัดไป การเรียกมันได้Unknown toolการดำเนินการแบบautomationไม่เคยเป็นเครื่องมือ - ทั้งสองช่องทางให้ชื่อเครื่องมือ schema annotation ผลลัพธ์ และการปฏิเสธแบบเดียวกัน ต่างกันเพียงเครื่องมืออัปโหลด: ที่นี่ใช้ base64 ส่วนอะแดปเตอร์ stdio ใช้ path ของไฟล์ในเครื่อง
- ข้อมูลรับรองสร้างโดยผู้ที่ลงชื่อเข้าใช้ใน การตั้งค่า → API หรือโดยการเชื่อมต่อแอป
เชื่อมต่อแอป
หัวข้อที่มีชื่อว่า “เชื่อมต่อแอป”ไคลเอนต์ MCP ที่รองรับ OAuth ให้ผู้ใช้ลงชื่อเข้าใช้ด้วยบัญชีของตัวเอง ไม่ต้องติดตั้งอะไร
- ไคลเอนต์เรียกที่อยู่ MCP โดยไม่มีโทเค็นและได้
401พร้อมWWW-Authenticate: Bearer error="invalid_token", resource_metadata="…" - ไคลเอนต์อ่าน
/.well-known/oauth-protected-resource/api/manage/mcpและ/.well-known/oauth-authorization-serverลงทะเบียนตัวเองที่POST /api/oauth/registerและเปิดหน้า/connectของหน้าผู้ดูแล - ผู้ใช้ลงชื่อเข้าใช้หากจำเป็น เลือกไซต์ คงหรือเอาเครื่องหมายออกจากสิ่งที่แอปขอ แล้วกด เชื่อมต่อ ส่วน ยกเลิก จะส่ง
access_deniedกลับไปยังแอป - ไคลเอนต์แลกโค้ดที่
POST /api/oauth/tokenและเรียกที่อยู่ MCP ด้วย access token
กฎ:
- ใช้ OAuth 2.1 authorization code กับ PKCE
S256เท่านั้น เฉพาะ public client: ไม่มี client secret - Redirect URI ต้องตรงทุกตัวอักษร:
https;httpบน127.0.0.1หรือlocalhostพอร์ตใดก็ได้; หรือ private-use scheme ของแอปเดสก์ท็อป (RFC 8252 7.1) เช่นcursor://…หรือcom.example.app:/cbprivate scheme ต้องเป็นตัวพิมพ์เล็ก ยาว 2 ถึง 64 ตัวอักษร และไม่ใช่javascript,data,file,vbscript,blob,about,filesystem,ftp,ws,wss,mailto,tel,sms,intent,chrome,chrome-extensionหรือview-sourceไม่มี fragment ไม่มี user info และไม่เกิน 5 รายการ หน้ายินยอมแสดงว่าแอปจะกลับไปที่ใด ลิงก์ที่ระบุแอปที่ไม่รู้จักหรือ redirect URI ที่ไม่ได้ลงทะเบียนจะได้คำตอบบนหน้านั้นและไม่ถูก redirect เลย - โค้ดมีอายุ 60 วินาทีและใช้ได้ครั้งเดียว การใช้ครั้งที่สองจะเพิกถอนสิ่งที่ครั้งแรกออกให้
- Access token (
dwo_) มีอายุ 10 นาที Refresh token (dwr_) มีอายุ 30 วันและหมุนเวียนทุกครั้งที่ใช้ refresh token แต่ละตัวใช้ได้ครั้งเดียว การส่ง refresh token ใดก็ตามที่ถูกแทนที่แล้ว ไม่ว่าจะหมุนเวียนไปกี่ครั้ง จะเพิกถอนการเชื่อมต่อ เก็บเฉพาะ digest แบบ SHA-256 - แอปที่ขอขอบเขตที่เซิร์ฟเวอร์ไม่รู้จักจะได้
invalid_scopeแอปที่ไม่ขอขอบเขตใดจะได้รับการเสนอทุกขอบเขต POST /api/oauth/revoke(RFC 7009) เพิกถอนการเชื่อมต่อทั้งหมดจากโทเค็นใดก็ได้- การเชื่อมต่อทำงานเหมือนข้อมูลรับรองที่ผู้ใช้คนนั้นสร้างด้วยขอบเขตที่อนุมัติ สำหรับทุกโมเดล
content:readรวมอยู่เสมอ สิทธิ์ปัจจุบันของผู้ใช้ถูกอ่านใหม่ทุกคำขอ: ผู้ใช้ที่ถูกปิดใช้งาน สมาชิกภาพที่ถูกนำออก หรือไซต์ที่ไม่ active จะหยุดการเชื่อมต่อทันที - สมาชิกทุกคนของไซต์เชื่อมต่อแอปได้ในนามตัวเอง และให้สิทธิ์ได้เฉพาะที่ตัวเองมี
- การตั้งค่า → API → แอปที่เชื่อมต่อ แสดงการเชื่อมต่อ: ของทุกคนเมื่อมี
site:settingsมิฉะนั้นเฉพาะของคุณ เพิกถอน หยุดการเชื่อมต่อทันที «ใช้ล่าสุดเมื่อ…» คือการแลกโทเค็นครั้งล่าสุด - การลงทะเบียนแบบ dynamic, token และ revocation ถูกจำกัดอัตราต่อ IP การเชื่อมต่อ โค้ด และไคลเอนต์ที่ไม่มีใครใช้จะถูกลบโดยการล้างรายวันหลัง 30 วัน
- ใน production Worker ตอบ
<host>/api/*และ<host>/.well-known/oauth-*
ความสำเร็จคือ { "data": … } (รายการเวอร์ชัน: { "data": [...], "next_cursor": … }) การปฏิเสธคือ { "error": "…", "reason": "…" } พร้อมรายละเอียด ให้แยกกรณีด้วย reason ส่วน error มีไว้ให้คนอ่าน
| สถานะ | เหตุผล | ความหมาย |
|---|---|---|
| 400 | invalid_request |
เนื้อหา query หรือ header ไม่ตรงกับ v1 |
| 400 | idempotency_key_required |
คำสั่งนี้ต้องมี Idempotency-Key |
| 401 | credential_invalid |
ไม่มี secret ไม่รู้จัก หรือถูกเพิกถอนแล้ว |
| 403 | creator_unauthorized |
ผู้สร้างไม่ได้ใช้งานหรือเสียสิทธิ์ในไซต์ หรือไซต์ไม่ได้ใช้งาน |
| 403 | scope_refused |
ข้อมูลรับรองไม่มีขอบเขตนั้น (required ระบุไว้) |
| 403 | listing_owner_restricted |
ผู้สร้างอ่านได้เฉพาะรายการของตัวเองในโมเดลนั้น |
| 409 | idempotency_in_progress |
คีย์เดียวกันยังทำงานอยู่ ลองใหม่หลัง Retry-After ถ้าไม่มี การจองจะค้างอยู่ ให้ตรวจผลแล้วใช้คีย์ใหม่ |
| 429 | rate_limited |
รอตาม Retry-After |
| 503 | audit_unavailable |
บันทึกความตั้งใจก่อนดำเนินการลงประวัติการตรวจสอบไม่ได้ จึงยังไม่เรียกใช้คำสั่ง |
| 503 | temporary_failure |
คำขอทำไม่เสร็จ error บอกสิ่งที่รู้: การเขียนการตั้งค่าหรือการเผยแพร่อาจถูกบันทึกแล้วแต่ยังไม่ได้บันทึกผล คำสั่งที่มี key ให้ลองใหม่ด้วย key และ body เดิม |
เหตุผลของ lifecycle และหน้าส่งผ่านโดยไม่เปลี่ยน เช่น not_found, model_denied, not_owner, field_denied, locale_denied, export_limited_role, forbidden, missing_permission, version_conflict, workflow_conflict, idempotency_key_reused, scheduled_target_stale, transition_kind_mismatch, illegal_transition, dependents_not_acknowledged, invalid_save_request, facts_conflict, no_changes, content_live, plugin_policy_refused, plugin_policy_unavailable, plugin_policy_budget_exhausted, plugin_policy_transform_invalid, plugin_policy_transform_conflict, media_in_use, media_usage_unreadable, media_framing_not_image, in_trash, not_in_trash, entry_in_trash, owner_language, restore_conflict, locale_exists, unknown_locale, language_changed, locked, lock_lost, draft_conflict, assignee_cannot_act, due_date_past, batch_relation_terms_invalid, batch_relation_field_invalid, batch_expected_versions_invalid, related_from_field_invalid, related_from_source_invalid, related_from_replace_invalid และ related_from_replace_unconfirmed
field_validation_failed (422) และ field_value_taken (409) มี locale และ
issues: [{ field, message, key?, params? }] ด้วย field คือ path เต็ม (faqs[2].question)
message เป็นภาษาอังกฤษ key และ params ระบุข้อความของ admin ที่ใช้สร้างประโยคนั้น เพื่อให้
admin แสดงเป็นภาษาของตัวเอง ผู้เรียกอื่นไม่ต้องสนใจค่าเหล่านี้
บันทึกตรวจสอบ
หัวข้อที่มีชื่อว่า “บันทึกตรวจสอบ”ทุกการเขียนบันทึกเจตนาก่อนทำงานและผลลัพธ์หลังทำงานในประวัติการดูแลระบบของไซต์ ข้อยกเว้นคือการ launch ปลั๊กอิน และ route ของ lease การแก้ไขและ draft ร่วม; draft ร่วมคืองานที่ยังไม่บันทึก ไม่ใช่เวอร์ชัน ผู้กระทำคือผู้สร้าง และ change.management_access ระบุข้อมูลรับรอง การดำเนินการ: management.content.save, management.content.transition, management.content.move, management.content.assign, management.content.locale.add, management.content.version.restore, management.content.trash, management.content.trash.restore, management.content.purge, management.content.relate, management.content.related_from, management.menu.save, management.menu.language_add, management.page.save, management.page.translation.start, management.media.upload, management.role.create, management.role.update, management.role.delete, management.media.folder.create, management.media.folder.update, management.media.folder.delete, management.media.move, management.media.update, management.media.delete, management.import.source.create, management.import.create, management.import.upload, management.import.advance, management.import.mapping, management.import.assignment, management.import.pause, management.import.resume, management.import.cancel, management.import.undo, management.export.create, management.export.advance, management.proposal.create, management.schema_proposal.create, management.model.apply, management.model.delete, management.settings.update และ management.credential.issue / management.credential.revoke สำหรับตัวข้อมูลรับรองเอง และ management.oauth.connect / management.oauth.revoke สำหรับแอปที่เชื่อมต่อ การเขียนของแอปที่เชื่อมต่อยังมี change.via ซึ่งเป็นชื่อแอป ประวัติจึงอ่านว่า “ผู้ใช้ผ่านแอป” เวอร์ชันและเหตุการณ์ workflow ระบุผู้สร้าง เหมือนการแก้ไขในหน้าผู้ดูแล เวอร์ชันของข้อเสนอที่ถูกนำไปใช้ระบุผู้ที่นำไปใช้ ข้อเสนอ schema ที่ถูกนำไปใช้คือ model.apply ตามปกติโดยผู้ที่บันทึก และ change.proposal ระบุข้อเสนอนั้น
สิ่งที่ไม่มีใน v1
หัวข้อที่มีชื่อว่า “สิ่งที่ไม่มีใน v1”- การเปลี่ยน workflow และการเผยแพร่แบบข้ามขั้นตอน
- ความลับ วิธีเข้าสู่ระบบ ผู้ให้บริการตัวตน การ deploy ผู้ให้บริการซอร์ส และโดเมน
- การดูแลปลั๊กอินและคำสั่งของปลั๊กอิน การติดตั้ง การให้สิทธิ์ การปิด และงานของปลั๊กอินเองใช้หน้าแอดมินและโปรโตคอลปลั๊กอินแยกต่างหาก API การจัดการเพียงแสดงปลั๊กอิน สร้างลิงก์เปิด และเรียกใช้การดำเนินการที่ประกาศไว้
- CRUD ของแบ็กเอนด์ที่สร้างขึ้น ซึ่งไม่ใช่ช่องทางจัดการ CMS เด็ดขาด
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”curl -s https://cms.example/api/manage/v1/entries \ -H "Authorization: Bearer $DEE_WAN_MANAGEMENT_TOKEN" \ -H "Idempotency-Key: create-hello-1" \ -H "Content-Type: application/json" \ -d '{"model_id":"<model id>","locales":{"en":{"title":"Hello"}}}'ขั้นตอนถัดไป
หัวข้อที่มีชื่อว่า “ขั้นตอนถัดไป”เว็บไซต์ของคุณเชื่อมต่อแล้ว เพื่อให้ทำงานต่อเนื่อง ให้ตั้งค่า การสำรองและกู้คืน เพื่อขยาย Dee Wan เริ่มที่ การเขียนปลั๊กอิน แบ็กเอนด์อิสระ ยังเป็นทางเลือก
ยังไม่เปิดตัว (pre-MVP) และยังไม่มีเวอร์ชันเสถียร
คอมมิตซอร์สโค้ด: fe23db481c422b40e317b92fc98116b4c0c26a87 (มีการแก้ไขที่ยังไม่คอมมิต)
วันที่สร้างเอกสาร (UTC): 2026-10-18T09:22:34.630Z
คอมมิตสำหรับอัปเกรด: ระบุไม่ได้: มีการแก้ไขที่ยังไม่คอมมิต. บันทึกการเปลี่ยนแปลง