@FordAntiTrust

 

สร้างปฏิทินงานไอดอลใช้เอง

ก่อนอื่น ต้องบอกหากหายจากการเขียน blog ไปเป็นปี แล้วโผล่มาก็เขียนอะไรก็ไม่รู้ แต่ทนๆ อ่านหน่อยนะ 🤣

จุดเริ่มต้น: ความวุ่นวายของคนตามไอดอล

ผมตามไอดลอสายจิกะ (坂道) มาสักพักใหญ่ ๆ ซึ่งคนที่ตามวงเหล่านี้รู้ดีว่า ตารางงานมันเยอะมากและกระจัดกระจาย มีหลายวง หลายช่วงเวลา เราในฐานะผู้ติดตามก็แค่อยากรู้ว่า "วงที่ชอบขึ้นเวทีกี่โมง? และซ้อนทับกับใครบ้าง หากมีหลายเวที" และงานที่ทำให้ผมประสบปัญหามากที่สุดคืองาน Japan Expo ที่ CTW เมื่อเดือนกุมภาพันธ์ที่ผ่านมา โดยงานก็ไม่ได้ให้ข้อมูลที่ดูได้ง่ายนัก ผมจึงทำระบบนี้ขึ้นมาใช้งานเอง ซึ่งก็ได้รับความสนใจจากเพื่อนๆ แฟนคลับอยู่พอสมควร

และพอจบงานดังกล่าว ก็ทำให้รองรับงานที่มีทุกอาทิตย์แทน และปรับปรุงเรื่อยมาโดยให้ตอบโจทย์ความต้องการส่วนตัวเป็นหลัก (คนทำใช้เอง รู้จุดว่าต้องการอะไร)

Idol Stage Timetable คืออะไร

stage-idol-calendar คือระบบปฏิทินอีเวนต์แบบ Open Source ที่ผมออกแบบมาเพื่อจัดการตารางงานแสดงไอดอลโดยเฉพาะ ตอนนี้ใช้งานจริงอยู่ที่ Idol Track — Live Idol TH และยังเอาไปประยุกต์ใช้งานได้หลากหลายอีกด้วย

สิ่งที่มันทำได้ มีอะไรบ้าง:

ตารางงาน

กรอง & ค้นหา

ศิลปิน & ติดตาม

Export & Subscribe

แชร์ & แสดงผล

ภาษา & ธีม

สำหรับผู้จัดงาน (Admin)

เบื้องหลังฟีเจอร์ที่ผมภูมิใจ

Anonymous Favorites — เพราะแค่อยากรู้ว่าวงตัวเองขึ้นกี่โมง

อันนี้เป็นฟีเจอร์ที่ผมอยากได้มาตลอด ในฐานะคนดูที่แค่อยากรู้ว่าวงที่ตามขึ้นเวทีเมื่อไหร่ แต่ปัญหาคือ เว็บแบบนี้มันไม่ควรบังคับให้คนสมัคร Account เพื่อกดติดตามศิลปิน ผู้ชมงานแค่อยากรู้ตารางเวลา ไม่ได้อยากมาจำรหัสผ่านอีกที่ อีกอย่าง จะได้ไม่ต้องวุ่นวายเรื่องการบริหารข้อมูลส่วนบุคคล

หลักคิด: ไม่มี Account แต่ต้อง Secure

ผมออกแบบให้ระบบ Favorites ทำงานโดยไม่มี Database สำหรับ User เลย ไม่มีตาราง users ไม่มี Login ทั้งหมดทำงานผ่าน Token-Based System บน File Storage

กระบวนการทำงานคือ:

  1. สร้าง Token — เมื่อผู้ใช้กด Follow ศิลปินคนแรก ระบบจะสร้าง UUID v7 (time-ordered UUID ที่ sort ตาม timestamp ได้) แล้วนำไป Sign ด้วย HMAC-SHA256 โดยใช้ Secret Key ที่เก็บไว้ฝั่ง Server

  2. Slug = UUID + HMAC ตัดสั้น — ถ้าใครแก้ UUID แล้ว HMAC ไม่ตรง ระบบจะ Reject ทันที ป้องกันการ Enumerate URL ไปดู Favorites ของคนอื่น

  3. เก็บเป็นไฟล์ JSON แบบ Sharded — ข้อมูล Favorites ของแต่ละคนเก็บเป็นไฟล์ .json บนโครงสร้าง Sharded Directory เพื่อกระจายไฟล์ไม่ให้โฟลเดอร์เดียวมีไฟล์เป็นหมื่น โดยข้อมูลในไฟล์ก็แค่ Array ของ Artist ID ที่ Follow ไว้

  4. localStorage เป็นแค่ Shortcut — Slug จะถูก Save ลง localStorage ของ Browser ด้วย Key หนึ่ง เพื่อให้ครั้งหน้าเปิดเว็บมา ระบบรู้ว่าเป็นคนเดิม แล้วแสดงเมนูระบบ Favorites ที่มุมบนซ้ายของทุกหน้า — โดยที่ localStorage ไม่ใช่ Source of Truth จริง ๆ มันเป็นแค่ Cache ฝั่ง Client ข้อมูลจริงอยู่ที่ไฟล์ JSON บน Server

  5. TTL 365 วัน + Auto-Touch — ไฟล์ .json มีอายุ 1 ปี แต่ทุกครั้งที่เข้ามาดูหน้า My Favorites หรือ My Upcoming Programs ระบบจะ Touch ไฟล์ (อัปเดต mtime) ให้นับ 365 วันใหม่ ตราบใดที่ยังเข้ามาใช้อยู่ ข้อมูลจะไม่หาย

  6. Garbage Collection แบบ Probabilistic — ฟังก์ชัน cleanup จะสุ่มรันทุกครั้งที่มี Request เข้ามา (ด้วยความน่าจะเป็นต่ำ) เพื่อลบไฟล์ที่หมดอายุออก ไม่ต้องตั้ง Cron Job แยก

Recovery UX — ไม่ต้องเปิด DevTools

ปัญหาที่เจอคือ ถ้า Token ใน localStorage หมดอายุ หรือ User เคลียร์ Browser Data หน้า My Favorites จะเปิดไม่ได้ ตอนแรกต้องเปิด DevTools ลบ key ใน localStorage เอง ซึ่งไม่ User-Friendly เลย ผมเลยเพิ่ม Recovery UX — เมื่อ Token ไม่ Valid ช่วยให้คนใช้งานแก้ไขได้ด้วยตัวเอง นอกจากนี้ยังมี Silent Self-Healing — ทุกครั้งที่แสดงเมนู Favorites มันจะไปเช็ค Slug กับ Server เบื้องหลัง ถ้าได้ 400/404 กลับมา (Token หมดอายุหรือไม่มีอยู่) ก็จะลบข้อมูลใน key จาก localStorage และเอาปุ่มออกเงียบ ๆ ไม่ต้องรอให้ User เจอหน้า Error

ทำไมไม่ใช้ Database? กับระบบ Favorites

เพราะหลักการของโปรเจกต์นี้คือ "ไม่มี Dependencies ที่ไม่จำเป็น" ระบบทั้งหมดรันบน SQLite ตัวเดียว ถ้าเพิ่มตาราง Users + Favorites ลง SQLite มันก็ทำได้ แต่จะเกิดปัญหาอื่น — ต้องมี Session Management สำหรับ Anonymous User, ต้อง GC Session ที่หมดอายุ, ต้อง Handle Race Condition ตอน Write พร้อมกัน (SQLite Lock) สำหรับข้อมูลที่ไม่ Critical อย่าง Favorites List การใช้ File-Based Storage + HMAC Validation มันง่ายกว่า เร็วกว่า และ Scale ได้ดีพอสำหรับ Use Case นี้ (เผื่อรองรับการใช้ Object storage ในอนาคตด้วย จะได้สเกลได้)

สรุปแล้ว Favorites ของแต่ละคนเป็นแค่ไฟล์ .json ขนาดไม่กี่ร้อย Bytes ที่อยู่ในโฟลเดอร์ที่ Protected ด้วย (Deny from all) อ่านไม่ได้จาก Web โดยตรง ต้องผ่าน API เท่านั้น ซึ่ง API ก็ Validate HMAC ทุก Request

สิ่งที่ต่อยอดจาก Favorites

พอมี Favorites แล้ว ผมก็ต่อยอดฟีเจอร์ได้อีกหลายตัว:

ทั้งหมดนี้ทำงานได้โดยไม่มี User Account ไม่มี Login ไม่มี Password — แค่ URL เดียวที่ Bookmark ไว้

Server-Side Image Export — แชร์ตารางสวย ๆ โดยไม่พึ่ง JavaScript

ตอนแรกผมใช้ html2canvas แต่มันมีปัญหาเรื่อง layout บนมือถือกับบนคอมพิวเตอร์มันไม่เหมือนกันเลยเปลี่ยนมา Render ฝั่ง Server ด้วย PHP GD ทั้งหมด สร้าง Three-Font Architecture ที่แยก Thai/Latin, CJK (ญี่ปุ่น/จีน), และ Symbol ออกจากกัน ระบบจะวิเคราะห์แต่ละตัวอักษรแล้วเลือกฟอนต์ที่ถูกต้อง ภาพที่ได้จะเปลี่ยนสีตามธีมของแต่ละงานด้วย มี Cache 1 ชม. เพื่อไม่ให้ Server หนัก

Live Stream Links — เพราะยุคนี้งานไอดอลไม่ได้มีแค่ On-site

หลายค่าย หลายวงมีตารางงานให้เมมเบอร์มี Live ไม่ว่าจะ IG Live, X Spaces หรือ YouTube Live ผมเลยเพิ่มระบบ Stream URL เข้าไป

ICS Feed ที่ต่อสู้กับ Outlook

เรื่องนี้สนุก ตอน Implement Subscription Feed ผมเจอบั๊กที่ทำให้ Outlook ลบอีเวนต์ทั้งหมดออกจากปฏิทินทุกรอบที่ Sync เพราะ ORGANIZER;CN="..." มีการ Escape เครื่องหมาย Comma ผิดวิธี (RFC 5545 QUOTED-STRING ไม่ใช้ Backslash Escaping) Outlook Parser เจอ Backslash แปลก ๆ ก็ Reject ทั้งไฟล์ แล้วก็เคลียร์ข้อมูลเก่าออก สุดท้ายแก้โดยเอา ORGANIZER ออกจาก VEVENT ไปเลย เพราะมันเป็น Optional นอกจากนี้ยังต้องจัดการ Line Folding ให้ถูกตาม RFC 5545 (ทุกบรรทัดเกิน 75 bytes ต้องตัด โดยไม่ตัดกลาง Multi-byte character) และแก้ CATEGORIES Delimiter ที่ Escape Comma ผิดทำให้ Outlook รวมศิลปินหลายคนเป็นชื่อเดียว ทุกอย่างมี Test ครอบคลุมหมด

ทำไมถึงเลือก PHP + SQLite + Vanilla JS (ไม่มี Framework)

หลายคนอาจสงสัย ทำไมไม่ใช้ React, Next.js หรือ Framework อะไรสักตัว

คำตอบง่าย ๆ คือ: ผมอยากให้มันเบา, Deploy ง่าย, ใครจะเอาไปใช้ก็แค่มี PHP กับ SQLite ไม่ต้อง Build, ไม่ต้องหา Database Server แยก จะรันบน Shared Hosting ราคาถูก ๆ ก็ได้ หรือจะรันผ่าน Docker ก็มี docker-compose ให้พร้อม

ตัว SQLite เหมาะกับระบบแบบนี้มาก เพราะข้อมูลไม่ได้เยอะขนาดต้องไปใช้ PostgreSQL/MySQL ที่มันยิ่งใหญ่เกินไป แต่เร็วกว่าอ่าน .ics ไฟล์ตรง ๆ ประมาณ 10-20 เท่า

สู่ Open Source

โปรเจกต์นี้เกิดจากความต้องการส่วนตัว 100% ผมอยากมีที่เดียวที่ดูตารางงานไอดอลได้ง่าย ๆ แต่พอทำเสร็จ ผมคิดว่ามันน่าจะมีประโยชน์กับคนอื่นด้วย ไม่ว่าจะเป็นผู้จัดงานที่อยากมี Timetable ให้ผู้ชม หรือแฟนคลับที่อยากจัดการข้อมูลงานของตัวเอง เลยเปิดเป็น Open Source ภายใต้ MIT License ใครจะเอาไปใช้ ดัดแปลง หรือแม้แต่ใช้เชิงพาณิชย์ก็ได้เลย

สิ่งที่อยากทำต่อไป

ลองใช้กันดู

ลอง Star ⭐ ให้กำลังใจกันได้ หรือจะ Fork ไปใช้กับงานอีเวนต์ของตัวเองก็ยินดีมาก ๆ ครับ

🌸 Made with ❤️ for event organizers and idol fans everywhere

Exit mobile version