N

Nokfa Docs

ไม่มีชื่อบทความ

🧭 Flow การเปลี่ยน Workspace (เมื่อใช้ Database Session)

🎯 เป้าหมาย:

ให้ผู้ใช้สามารถกดเปลี่ยน Workspace แล้ว session จะอัปเดต currentWorkspace และ frontend รู้ทันที


🔄 1. ภาพรวม UX Flow

sequenceDiagram
  participant UI
  participant API (/api/auth/switch-ws)
  participant Adapter (FirestoreAdapter)
  participant Firestore

  UI->>API: POST /switch-ws { workspaceId }
  API->>Adapter: updateSession({ sessionToken, currentWorkspace })
  Adapter->>Firestore: PATCH sessions/{sessionToken}
  Firestore-->>Adapter: ok
  Adapter-->>API: success
  API-->>UI: 200 OK
  UI->>NextAuth: session revalidate
  NextAuth-->>UI: session.currentWorkspace = "new_id"

🗂 2. โครงสร้างที่เกี่ยวข้องใน Firestore

Path ใช้ทำอะไร
gohig/auth/sessions/{sessionToken} บันทึก field currentWorkspace
gohig/app/users/{userId} มี defaultWorkspace (ใช้ตอนแรกเข้า)
gohig/app/memberships/{id} ตรวจสอบว่า user เป็นสมาชิกของ workspace นั้นหรือไม่

🧱 3. การเปลี่ยน Workspace (ฝั่ง API Route)

// /app/api/auth/switch-ws/route.ts

import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { getAuthAdapter } from '@/lib/adapter';
import { db } from '@/lib/firebase-admin';
import { collection, query, where, getDocs } from 'firebase-admin/firestore';

export async function POST(req: Request) {
  const { workspaceId } = await req.json();
  const session = await getServerSession(authOptions);
  if (!session?.user?.id || !session.sessionToken) {
    return new Response('Unauthorized', { status: 401 });
  }

  // ตรวจสอบว่าผู้ใช้อยู่ใน workspace นี้หรือไม่
  const membershipsRef = collection(db, 'gohig/app/memberships');
  const snap = await getDocs(
    query(membershipsRef,
      where('userId', '==', session.user.id),
      where('workspaceId', '==', workspaceId)
    )
  );

  if (snap.empty) {
    return new Response('Forbidden: not a member', { status: 403 });
  }

  // อัปเดต session ใน Firestore
  const adapter = getAuthAdapter();
  await adapter.updateSession({
    sessionToken: session.sessionToken,
    userId: session.user.id,
    expires: session.expires,
    currentWorkspace: workspaceId,
  });

  return Response.json({ success: true });
}

🧑‍💻 4. ฝั่ง Client: เรียก API + Revalidate Session

'use client';

import { useSession } from 'next-auth/react';

export default function WorkspaceSwitcher() {
  const { data: session, update } = useSession();

  const switchWorkspace = async (wsId: string) => {
    const res = await fetch('/api/auth/switch-ws', {
      method: 'POST',
      body: JSON.stringify({ workspaceId: wsId }),
    });

    if (res.ok) {
      await update(); // 🔁 ดึง session ใหม่
    }
  };

  return (
    <select
      onChange={(e) => switchWorkspace(e.target.value)}
      value={session?.currentWorkspace ?? ''}
    >
      {/* ดึงจาก session หรือ fetch workspaces ก็ได้ */}
      <option value="workspace_xyz">Gohig</option>
      <option value="workspace_abc">My Personal</option>
    </select>
  );
}

🧠 5. เสริมความปลอดภัย

  • ตรวจสอบ workspace scope หรือ role ใน switch-ws API ได้เลย
  • ถ้า field currentWorkspace ไม่มีใน session → fallback เป็น defaultWorkspace
  • อย่าลืมระบุ sessionToken ใน getServerSession() ด้วย module augmentation (NextAuth ยังไม่ใส่มาให้ใน session type)

👉 module augmentation เพิ่ม sessionToken :

// next-auth.d.ts
declare module 'next-auth' {
  interface Session {
    sessionToken?: string;
    currentWorkspace?: string;
  }
}

✅ สรุปสิ่งที่ต้องทำ

สิ่งที่ต้องมี เพื่ออะไร
strategy: 'database' ให้ session อยู่ใน Firestore และเรียก updateSession() ได้
✅ API /api/auth/switch-ws อัปเดต session ด้วย workspace ที่เลือก
✅ field currentWorkspace ใน session ใช้เป็น context ตลอดการใช้งาน
✅ UI สลับ workspace + revalidate session ให้ UX ลื่นไหล
✅ ตรวจสิทธิใน memberships ป้องกันการสลับ workspace ที่ไม่มีสิทธิ