반응형
https://www.youtube.com/watch?v=Av9C7xlV0fA
05:22:41
1. Members 테이블 컬럼 구성
Appwrite에서 members 테이블을 생성하고 다음 컬럼들을 추가
- userId: 사용자 ID (string, 50자, 필수)
- workspaceId: 워크스페이스 ID (string, 50자, 필수)
- role: 역할 (Enum: ADMIN/MEMBER, 필수)
- Users와 Workspaces를 연결하는 중간 테이블 (Many-to-Many 관계)
- 한 사용자가 여러 워크스페이스에 속할 수 있고, 각각 다른 역할을 가질 수 있음



2. 환경 변수 설정 (.env.local), Config 파일 (src/config.ts)
.env.local
NEXT_PUBLIC_APPWRITE_MEMBERS_ID=members
src/config.ts
export const APPWRITE_ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!;
export const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT!;
export const DATABASE_ID = process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID!;
export const WORKSPACES_ID = process.env.NEXT_PUBLIC_APPWRITE_WORKSPACES_ID!;
export const MEMBERS_ID = process.env.NEXT_PUBLIC_APPWRITE_MEMBERS_ID!;
export const IMAGES_BUCKET_ID = process.env.NEXT_PUBLIC_APPWRITE_IMAGES_ID!;
3. MemberRole Enum (src/features/members/type.ts)
- TypeScript enum으로 역할 타입 정의
- 문자열 enum: 값 자체가 "ADMIN", "MEMBER"
export enum MemberRole {
ADMIN = "ADMIN",
MEMBER = "MEMBER"
}
4. Workspace 생성 API - POST (src/features/workspaces/server/route.ts)
- Workspaces: 워크스페이스 자체의 정보 (이름, 이미지 등)
- Members: "누가 어떤 워크스페이스의 멤버인가" 관계 정보
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { createWorkspaceSchema } from "../schemas";
import { sessionMiddleware } from "@/lib/session-middleware";
import { DATABASE_ID, IMAGES_BUCKET_ID, MEMBERS_ID, WORKSPACES_ID } from "@/config";
import { ID } from "node-appwrite";
const app = new Hono()
.get(
...
)
.post(
"/",
zValidator("form", createWorkspaceSchema),
sessionMiddleware,
async (c) => {
const databases = c.get("databases")
// storage 불러오고
const storage = c.get("storage")
const user = c.get("user")
const {name, image} = c.req.valid("form")
let uploadedImageUrl: string | undefined;
console.log('data', image);
if(image instanceof File){
const file = await storage.createFile(
IMAGES_BUCKET_ID,
ID.unique(),
image
);
uploadedImageUrl = file.$id
}
const workspace = await databases.createDocument(
DATABASE_ID,
WORKSPACES_ID,
ID.unique(),
{
name,
userId:user.$id,
imageUrl: uploadedImageUrl
}
);
// 워크스페이스 생성자를 자동으로 ADMIN 멤버로 등록
await databases.createDocument(
DATABASE_ID,
MEMBERS_ID,
ID.unique(),
{
userId: user.$id, // 생성한 사람
workspaceId: workspace.$id, // 방금 만든 워크스페이스
role: MemberRole.ADMIN // ADMIN 역할 부여
}
)
return c.json({data:workspace})
}
)
export default app
appwrite workspaces 목록 삭제하고 다시 등록
workspaces목록의 workspacesId와 userId가 members의 column과 동일함

5. Workspace 목록 조회 API - GET
src/features/workspaces/server/route.ts
get에서 member list도 불러온다? 가 맞나?
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { createWorkspaceSchema } from "../schemas";
import { sessionMiddleware } from "@/lib/session-middleware";
import { DATABASE_ID, IMAGES_BUCKET_ID, MEMBERS_ID, WORKSPACES_ID } from "@/config";
import { ID, Query } from "node-appwrite";
import { MemberRole } from "@/features/members/type";
const app = new Hono()
.get(
"/",
sessionMiddleware,
async (c) => {
const user = c.get("user")
const databases = c.get("databases")
// 1단계: 현재 사용자의 멤버십 찾기
const members = await databases.listDocuments(
DATABASE_ID,
MEMBERS_ID,
[Query.equal("userId", user.$id)]
);
// 2단계: 멤버십이 없으면 빈 배열 반환
if(members.total === 0){
return c.json({data: {documents: [], total:0}})
}
// 3단계: 멤버십에서 워크스페이스 ID들 추출
const workspaceIds = members.documents.map((member) => member.workspaceId)
// 4단계: 해당 워크스페이스들만 조회
const workspace = await databases.listDocuments(
DATABASE_ID,
WORKSPACES_ID,
[
Query.orderDesc("$createdAt"), // 최신순 정렬
Query.contains("$id", workspaceIds) // ID가 목록에 있는 것만
]
);
return c.json({data:workspace})
}
)
.post(
...
)
export default app
- 사용자 A: 자기가 속한 워크스페이스만 보임
- 사용자 B: 다른 사람의 워크스페이스는 안 보임 (권한 격리)


6. 로그아웃 시 캐시 무효화 (src/features/auth/api/use-logout.ts)
- React Query 캐시에서 "current" (현재 사용자 정보) 무효화
- 다음에 current를 조회하면 새로 fetch함
onSuccess: () => {
toast.success("Logged out")
router.refresh()
queryClient.invalidateQueries({queryKey :["current"]}) // 데이터를 강제로 "무효화"시켜서 다시 가져온다.
queryClient.invalidateQueries({queryKey :["workspaces"]})
// window.location.reload()
},
7. 초대 코드 생성 유틸 (src/lib/utils.ts)
src/lib/utils.ts
export function generateInviteCode(length: number){
const characters = 'ABCDEFGHIJKLMOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for(let i=0; i <length; i++){
result += characters.charAt(Math.floor(Math.random()*characters.length))
}
return result
}
8. Workspace 생성 시 초대 코드 추가
src/features/workspaces/server/route.ts
- 워크스페이스 생성할 때 자동으로 10자리 초대 코드 생성
- 이 코드를 나중에 다른 사람에게 공유하면 워크스페이스에 초대할 수 있음
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { createWorkspaceSchema } from "../schemas";
import { sessionMiddleware } from "@/lib/session-middleware";
import { DATABASE_ID, IMAGES_BUCKET_ID, MEMBERS_ID, WORKSPACES_ID } from "@/config";
import { ID } from "node-appwrite";
const app = new Hono()
.get(
...
)
.post(
...
const workspace = await databases.createDocument(
DATABASE_ID,
WORKSPACES_ID,
ID.unique(),
{
name,
userId:user.$id,
imageUrl: uploadedImageUrl,
inviteCode: generateInviteCode(10) // 추가!
}
);
return c.json({data:workspace})
}
)
export default app
appwrite의 workspaces 에 inviteCode column추가 후 members와 workspace목록 삭제.
재등록하면 inviteCode가 생성됨

반응형
'Clone Coding' 카테고리의 다른 글
| Jira-clone - 독립 실행형 레이아웃과 워크스페이스 생성 페이지 구현 (0) | 2025.10.10 |
|---|---|
| Jira-clone - 개별 작업 공간 ID 페이지 만들기 (0) | 2025.10.02 |
| Jira-clone - Workspace 목록 불러오기 (0) | 2025.10.01 |
| Jira-clone - image 업로드 (0) | 2025.09.29 |
| Jira-clone - Toaster 알림 라이브러리사용 (0) | 2025.09.27 |