ระบบเว็บของกิจกรรมต้อนรับนักศึกษาใหม่ KKU63 — ลงทะเบียน e-ticket เช็คอิน แต้มสะสม และแดชบอร์ดสตาฟ ออกแบบบนฐานนักศึกษาใหม่ราว 8,500 คนและทีมงาน 500 คน




ระบบเว็บของกิจกรรมต้อนรับนักศึกษาใหม่ มหาวิทยาลัยขอนแก่น รุ่น KKU63 งานเดียวรวมตั้งแต่ลงทะเบียนกิจกรรม ออก e-ticket เช็คอินหน้างาน เก็บแต้ม จับกลุ่มเพื่อน ไปจนถึงแดชบอร์ดที่ทีมงานใช้คุมงานจริงตลอดสัปดาห์กิจกรรม ตัวเลขที่ใช้ออกแบบเขียนไว้ในเอกสารของ repo ตรง ๆ คือ นักศึกษาใหม่ราว 8,500 คน และสตาฟราว 500 คน
commit แรก 11 พฤษภาคม 2569 commit สุดท้าย 2 สิงหาคม 2569 รวม 364 commit ในสามเดือน ฝั่งเว็บมี 934 ไฟล์ TypeScript ประมาณ 124,000 บรรทัด และไฟล์เทสต์ 97 ไฟล์ นอกจากเว็บยังมีโฟลเดอร์ console เป็น Jupyter notebook สำหรับงานข้อมูลหลังบ้าน เช่น ดึงรายชื่อนักศึกษาใหม่ 3,138 คนจาก 148 หลักสูตร
ทุกฝั่งอยู่ในแอปเดียว แยกด้วยสิทธิ์ ไม่ได้แยกเป็นคนละเว็บ รวมแล้วประมาณ 65 หน้า
สิทธิ์ทั้งหมดผูกไว้กับ Firebase Auth custom claims — roleGroup, roleType, volunteerRole, friendshipGroup, isOnboarded, isVerified แล้วทุกหน้าประกาศเงื่อนไขของตัวเองเป็น AccessRequirement ที่คืนเหตุผลออกมาด้วยว่าโดนปฏิเสธเพราะอะไร ไม่ใช่แค่ผ่านหรือไม่ผ่าน
kku-63.vercel.app ล็อกอินผ่าน OAuth ของมหาวิทยาลัยเป็นหลักตัวเลข 8,500 กับ 500 ไม่ได้อยู่ในเอกสารไว้เฉย ๆ มันเป็นตัวคูณ หน้าของผู้เข้าร่วมที่อ่าน 20 document กลายเป็นราว 170,000 ครั้งถ้านักศึกษาใหม่เปิดกันคนละครั้ง หน้าสตาฟที่อ่าน 500 document กลายเป็นราว 250,000 ครั้งถ้าทีมงานเปิดครบ รูปร่างของ query เลยถูกนับเป็นเรื่องความถูกต้อง ไม่ใช่เรื่องที่ค่อยมาปรับตอนท้าย
orderBy และ limit เสมอ และใช้ cursor เมื่อข้อมูลโตได้byIds แบบแบ่ง chunk แล้ว reuse map เดิมภายใน request เดียวแทนการอ่านซ้ำevent/{eventId}/counters/e-ticket แทนการนับจากตั๋วทั้งงาน พร้อมเทสต์กรณีเช็คอินพร้อมกันkku-location และ event มี cache ชั้น serviceเขียน query ให้แคบเป็นงานที่ทำได้ในหนึ่งบ่าย แต่รักษาไว้ด้วยความตั้งใจอย่างเดียวไม่ได้ เพราะการเขียน listServerUserPublic() แล้ว filter ต่อ มันสั้นกว่าและง่ายกว่าเสมอ สิ่งที่ทำคือทำให้เขียนแบบนั้นแล้วไม่ผ่าน
web/scripts/firestore-guard.mjs บล็อก listServerX() นอก allowlist
web/eslint.config.mjs ห้าม server-only import client repository
web/AGENTS.md กฎสั้น ๆ สำหรับ AI agent
docs/firestore-query-knowledge.md เหตุผล ตัวอย่าง และ decision tree
docs/firestore-read-guardrails.md เช็คลิสต์ตอนรีวิว PRallowlist ของ guard เก็บ path ที่อ่านทั้ง collection ได้จริง ๆ ไว้เป็นรายชื่อ พร้อมเหตุผลกำกับ การเพิ่มชื่อเข้าไปเลยกลายเป็นการตัดสินใจที่ต้องอธิบาย ไม่ใช่การพิมพ์โค้ดผ่าน ๆ
ถ้าคำตอบคือ “อ่านทั้ง collection แล้วค่อยกรองใน memory” คำตอบตั้งต้นคือไม่ สำหรับ hot path ของ participant และ staff
เช็คลิสต์ใน PR ถามคำถามเดิมทุกครั้ง — ถ้า collection นี้โต 10 เท่า query ยังอยู่ในขอบเขตไหม และโค้ดอ่าน document ชุดนี้เพราะ UI ต้องใช้จริง หรือเพราะยังไม่มี repository method ที่แคบพอ
เว็บปิดให้บริการถาวรเมื่อ 2 สิงหาคม 2569 หลังกิจกรรมจบ main ถูกล็อกไว้หลัง maintenance mode แล้วแตกสาขา demo ไว้ต่างหากสำหรับเปิดดู โดยมีหมายเหตุตัวหนาใน README ว่าอย่า merge กลับ และอย่าเปิดสาธารณะจนกว่าจะชี้ไปชุดข้อมูลเดโม เพราะโปรเจกต์นี้ต่ออยู่กับฐานข้อมูลที่มีข้อมูลนักศึกษาจริง รวมถึงข้อมูลแพ้ยา แพ้อาหาร และโรคประจำตัว
ปีที่สี่แล้วที่ทำระบบนี้ และเป็นปีแรกที่มีเทสต์ มีเอกสารที่บันทึกเหตุผลของการตัดสินใจไว้ และมีเครื่องมือที่ปฏิเสธความผิดพลาดเดิมได้เองโดยไม่ต้องรอให้ใครจำได้