2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00
2026-08-16 13:51:07 +07:00

WKM

แอปเว็บจัดการอีเวนท์กับเพื่อน: ปฏิทินส่วนตัว, กลุ่มแชท, และระบบ "หาเวลาว่าง" ที่ใช้ AI (Gemini) วิเคราะห์ตารางของสมาชิกทุกคน แล้วโพสต์ช่วงเวลาที่ทุกคนว่างตรงกันกลับเข้าแชท

ทุกอย่างเป็นภาษาไทย และใช้โซนเวลา Asia/Bangkok (UTC+7)


สแต็กหลัก

  • Framework: Next.js (App Router) + React + TypeScript
  • Runtime: Bun — รันด้วย bun --bun
  • DB: PostgreSQL + Drizzle
  • Auth: better-auth (email/รหัสผ่าน + Google + Turnstile captcha)
  • AI: Gemini (gemini-3.1-flash-lite) — ใช้กับระบบหาเวลาว่างเท่านั้น
  • UI: shadcn/ui + Tailwind, ฟอนต์ไทย IBM Plex Sans Thai

เริ่มต้นพัฒนา

  1. ติดตั้ง dependencies: bun install
  2. สร้าง .env — คัดลอกจากตารางตัวแปรด้านล่าง (จำเป็นจริง ๆ แค่ DATABASE_URL และ BETTER_AUTH_SECRET)
  3. ตั้งค่า DB (schema อยู่ใน db/schema.ts) แล้ว push schema:
    bunx drizzle-kit push
    
  4. รัน dev:
    bun --bun next dev
    

ตัวแปร environment

ตัวแปร จำเป็น? ใช้ทำอะไร
DATABASE_URL ✅ PostgreSQL connection string
BETTER_AUTH_SECRET ✅ secret เซสชัน
BETTER_AUTH_URL ⬜ base URL (default http://localhost:3000)
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET ⬜ Google OAuth (ถ้าไม่มี Google login ก็ใช้งานได้)
NEXT_PUBLIC_TURNSTILE_SITE_KEY / TURNSTILE_SECRET_KEY ⬜ Turnstile captcha
GEMINI_API_KEY ⬜ ถ้าไม่มี ระบบหาเวลายังทำงานได้แบบ deterministic

คำสั่งที่ใช้บ่อย

bun --bun next dev        # dev server (ห้ามรันพร้อมกันหลาย instance!)
bunx drizzle-kit generate # สร้าง migration ใหม่
bunx tsc --noEmit         # typecheck
bunx eslint <files>       # lint

โครงสร้าง — ดูไฟล์ไหนถ้าจะแก้เรื่องอะไร

app/
  calendar/          # ปฏิทินส่วนตัว (server actions + optimistic UI)
  group/             # แชท + กลุ่ม: sidebar, chat UI, หน้า Ranking
    [id]/ranking/    # Ranking ขาบิด
  auth/              # หน้า sign-in / sign-up
components/
  event-calendar/    # ปฏิทิน 4 มุมมอง + drag & drop
  free-time/         # Dialog "หาเวลาว่าง"
  group-events/      # บัตรนัดในแชท + จบอีเวนท์
  ranking/           # podium / list / tier badge
db/schema.ts         # Drizzle schema ทั้งหมด
lib/
  group-realtime.ts  # realtime: SSE + PG LISTEN/NOTIFY + presence
  free-time.ts       # engine คำนวณช่วงว่าง (deterministic — ต้องแม่น 100%)
  gemini.ts          # wrapper Gemini
  gemini-prompt.ts   # prompts + schema ให้ Gemini

สถาปัตยกรรมคร่าว ๆ

ข้อมูลหลัก

  • ผู้ใช้ (user) — ร่วมกับตารางของ better-auth (session, account, ...)
  • กลุ่ม (group, group_member) — กลุ่มแชท + สมาชิก (status: pending/accepted/declined)
  • แชท (group_message) — senderId เป็น NULL + senderKind='assistant' สำหรับข้อความ AI (AI ไม่มีบัญชี user)
  • ปฏิทินส่วนตัว (calendar_event) — ตารางเวลาของแต่ละคน
  • นัดกลุ่ม (group_event, group_event_attendance) — นัดหมายในแชท + ลงชื่อเข้าร่วม/บิด

ระบบหาเวลาว่าง (ฟีเจอร์หลัก)

หลักการสำคัญ: AI ไม่สร้างคำตอบเอง เพื่อการันตี "ไม่มีช่วงเวลาผิด"

  1. Engine ฝั่ง server (lib/free-time.ts) คำนวณช่วงว่างจากตารางเวลาจริงใน DB — แม่น 100%
  2. Gemini แค่ช่วย 2 อย่าง: แปลคำขอเป็นเงื่อนไข (interpretRequest) และจัดอันดับสล็อต (rankSlots)
  3. ผลลัพธ์ถูกโพสต์กลับเข้าแชทเป็นข้อความ AI (markdown)

ถ้า AI ล่มหรือไม่มี API key → fallback ไปใช้ deterministic sort (ยังทำงานถูกต้อง)

Realtime

ระบบ SSE + PostgreSQL LISTEN/NOTIFY (lib/group-realtime.ts) — dispatch ใน process ทันที + เผยแพร่ข้าม process ด้วย pg_notify สำหรับกรณีหลาย replica


ข้อควรระวังตอนแก้ code

  • ห้ามรัน dev server หลาย instance พร้อมกัน — realtime เป็นแบบ in-process
  • อย่าให้ AI คำนวณสล็อตเอง — ให้ AI ทำงานกับสล็อตที่ engine คำนวณให้เท่านั้น
  • ทุกการคำนวณเวลา ใช้ helper bangkokParts / dayStartBangkok (โซนเวลา UTC+7) ไม่ใช้ local time
  • เวลาที่เสนอ ใช้ day-band default 06:00–22:00 (กันเวลากลางคืน)
  • ทุก mutation ผ่าน server action + validate ด้วย zod + revalidatePath
  • AI ข้อความต้องเป็น senderId = NULL + senderKind = 'assistant' เสมอ
S
Description
No description provided
Readme
35 MiB
Languages
TypeScript 94.6%
Shell 3.3%
Perl 0.7%
CSS 0.7%
PLpgSQL 0.5%