
타임박싱을 혼자 쓰다 보니 욕심이 생겼습니다. "저녁 약속", "병원 예약", "아이 학원" 같은 일정을 가족끼리 공유할 수 있으면 좋겠다는 생각이요. 내 타임테이블에 와이프가 등록한 "마트 장보기"가 보이고, 와이프 타임테이블에 내가 등록한 "퇴근 후 픽업"이 보이는 구조를 만들어보겠습니다.
1. 환경 정보
| 항목 | 버전/스펙 |
|---|---|
| Backend | PocketBase (Self-hosted) |
| Frontend | HTML + Vanilla JS |
| Reverse Proxy | Nginx |
| 인프라 | Proxmox 홈랩 LXC 컨테이너 |
| AI 어시스턴트 | Claude |

2. 배경 — 왜 공유 일정이 필요했나
4자녀 가정이다 보니 일정이 복잡합니다. 학원 픽업, 병원 예약, 마트 장보기 같은 것들이 매일 겹치는데, 서로 어떤 일정이 있는지 확인할 방법이 없었습니다. 카카오톡으로 매번 물어보는 것도 한계가 있고, 기존 캘린더 앱은 너무 무겁습니다.
이미 타임박싱 앱이 돌아가고 있으니, 여기에 공유 기능만 얹으면 되겠다 싶었습니다.
3. 설계 — shared_events 컬렉션과 API 권한
3-1. 컬렉션 구조
기존 timebox_daily는 개인 데이터이므로, 공유 일정은 별도 컬렉션 shared_events로 분리했습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
| date | Text | 날짜 (YYYY-MM-DD) |
| start_slot | Number | 시작 슬롯 (0~83) |
| end_slot | Number | 끝 슬롯 (0~83) |
| title | Text | 일정 제목 |
| color_idx | Number | 색상 번호 |
| creator | Relation → users | 만든 사람 |
| targets | Relation → users (다중) | 공유 대상 |
targets가 비어있으면 전체 공유, 유저가 선택되어 있으면 해당 유저에게만 보이는 구조입니다.

3-2. API 권한 설계
가장 중요한 부분입니다. PocketBase의 API Rules로 "내가 볼 수 있는 일정만" 조회되도록 설정했습니다.
listRule: @request.auth.id != "" && (
targets ~ @request.auth.id || // 내가 대상에 포함
creator = @request.auth.id || // 내가 만든 것
targets:length = 0 // 전체 공유
)PocketBase API Rules는 SQL의 WHERE 절과 비슷한 문법으로 접근 제어가 가능합니다. targets ~ @request.auth.id는 다중 Relation 필드에서 현재 로그인한 유저 ID가 포함되어 있는지 체크하는 표현식입니다. 삭제 권한은 creator = @request.auth.id로 만든 사람만 가능하게 제한했습니다.
참고: PocketBase API Rules에 대한 자세한 문법은 공식 문서에서 확인할 수 있습니다.
4. 구현 — 공유 체크박스, 시각적 구분, DONE 토글
4-1. 공유 체크박스
PLAN 팝업에 "📤 공유 일정으로 등록" 체크박스를 추가했습니다. 체크하면 유저 목록이 나타나서 공유 대상을 선택할 수 있습니다. 아무도 선택하지 않으면 전체 공유입니다.

4-2. 시각적 구분
개인 일정과 공유 일정이 한눈에 구분되어야 합니다.
- 개인 일정: 파스텔 단색 블록
- 공유 일정: 파스텔 + 대각선 줄무늬 + 📤 라벨
CSS로 줄무늬 패턴을 만들었습니다.
.shared-ev {
background: repeating-linear-gradient(
135deg, transparent, transparent 3px,
rgba(0,0,0,.1) 3px, rgba(0,0,0,.1) 6px
);
}repeating-linear-gradient로 135도 각도의 대각선 줄무늬를 만들고, rgba(0,0,0,.1)로 살짝 어두운 줄을 넣었습니다. 기존 파스텔 배경색 위에 오버레이되면서 자연스럽게 "이건 공유 일정이구나"라는 시각적 신호를 줍니다.
4-3. DONE 토글
공유 일정도 클릭하면 완료 처리됩니다. 줄무늬가 진한 단색 + ✓로 바뀌고, 다시 클릭하면 줄무늬로 복원됩니다. 길게 누르면 상세 정보와 삭제 버튼이 나타나는데, 삭제는 만든 사람만 가능합니다.
5. 트러블슈팅 — 세 가지 삽질 기록
5-1. PocketBase 관리자 페이지 접속 불가
GUI 대시보드에서 컬렉션을 만들려고 관리자 페이지(/pb/_/)에 접속했는데 안 열렸습니다. 원인은 두 가지였습니다.
- Nginx 설정: 내부망만 허용하도록 제한해놨던 것
- PocketBase 바인딩:
127.0.0.1만 리스닝해서 컨테이너 IP로 접속 불가
결국 curl로 API를 직접 호출해서 컬렉션을 만들었습니다.
# 관리자 토큰 발급
curl -s http://127.0.0.1:8090/api/admins/auth-with-password \
-d '{"identity":"admin@email.com","password":"..."}'
# 컬렉션 생성
curl -s http://127.0.0.1:8090/api/collections \
-H 'Authorization: 토큰' \
-d '{"name":"shared_events","schema":[...]}'CLI에서 PocketBase를 관리하는 건 처음이었는데, REST API가 깔끔해서 할 만했습니다. GUI가 막히면 당황하지 말고 curl로 해결하면 됩니다.

5-2. Number 필드에 0을 넣으면 거부당하는 문제
start_slot: 0, color_idx: 0을 보내면 PocketBase가 "값이 없다"고 거부합니다. required 필드인데 0이 falsy로 취급되는 거죠.
이건 PocketBase의 알려진 이슈입니다. 모든 컬렉션 필드가 non-nullable이고 각 타입의 제로값을 기본값으로 사용하기 때문에, required 검증 시 0을 "비어있음"으로 판단합니다.
관련 이슈: GitHub Issue #907, #526
해결 방법: Number 필드의 required를 false로 변경했습니다. 어차피 date와 title만 필수면 충분합니다.
# required 해제 (curl로 스키마 수정)
curl -s -X PATCH http://127.0.0.1:8090/api/collections/shared_events \
-H 'Authorization: 토큰' \
-d '{"schema":[...required:false...]}'5-3. 유저 목록이 안 불러와지는 문제
공유 대상 선택을 위해 유저 목록을 불러와야 하는데, PocketBase users 컬렉션의 listRule이 기본적으로 "본인 정보만" 허용이었습니다. 다른 유저가 안 보입니다.
가족용 앱이니까 listRule을 "모든 로그인 유저"로 변경했습니다.
curl -s -X PATCH http://127.0.0.1:8090/api/collections/users \
-H 'Authorization: 토큰' \
-d '{"listRule":"@request.auth.id != \"\""}'⚠️ 주의: 이 설정은 가족용 비공개 앱이기 때문에 가능한 겁니다. 불특정 다수가 사용하는 서비스라면 유저 목록을 이렇게 열면 안 됩니다. 실제 서비스에서는 "그룹" 또는 "팀" 개념을 별도로 만들어서 같은 그룹 내 유저만 조회되도록 해야 합니다.
6. 공유 대상 선택 — 전체 공유 vs 특정 유저
처음에는 "전체 공유"만 있었습니다. 그런데 직접 써보니 "와이프에게만 보내고 싶은 일정"이 있더라고요. 아이들 계정에는 안 보여도 되는 것들이요.
그래서 특정 유저를 선택하는 기능을 추가했습니다.
targets필드가 비어있으면 → 전체 공유targets에 유저 ID가 있으면 → 해당 유저 + 생성자만 열람 가능
팝업에서 공유 체크박스를 켜면 유저 목록이 드롭다운으로 나타나고, 체크한 사람에게만 공유됩니다. PocketBase API Rules가 자동으로 필터링해주니까, 프론트엔드에서는 targets 배열만 넘기면 됩니다. 백엔드가 알아서 권한 체크를 해주는 구조라 프론트 코드가 깔끔하게 유지됩니다.

7. 마무리 — 배운 점과 다음 편 예고
이번 편에서 배운 것
- PocketBase API Rules는 강력합니다. SQL WHERE 비슷한 문법으로 접근 제어가 가능해서, 별도 미들웨어 없이도 권한 관리가 됩니다. 다중 Relation 필드 매칭(
~연산자)이나 필드 길이 체크(:length)도 지원합니다. - Number 필드에서 0은 함정입니다. PocketBase의 non-nullable 설계 때문에 required Number 필드에 0을 넣으면 검증 실패합니다. required를 끄거나 최소값을 명시해서 우회하세요.
- REST API만으로 PocketBase 관리가 가능합니다. GUI가 안 되면 curl로 컬렉션 생성, 스키마 수정, 규칙 변경까지 전부 할 수 있습니다. API가 깔끔해서 오히려 더 빠를 때도 있습니다.
- 공유 기능의 핵심은 "누가 뭘 볼 수 있는지"입니다. 기능 구현보다 권한 설계를 먼저 하는 게 맞습니다. 나중에 바꾸려면 데이터 마이그레이션이 필요해질 수 있으니까요.
참고 링크
- PocketBase 공식 문서 — API Rules and Filters
- PocketBase 공식 문서 — Working with Relations
- PocketBase GitHub — Number 필드 0 값 이슈 #907
- PocketBase API Rules Cheat Sheet — Discussion #3162
다음 편: #8 — 인프라 보안 & 백업 — HTTPS 설정, 자동 백업, 방화벽 구성 등 홈랩에서 실서비스를 운영할 때 챙겨야 할 보안 요소들을 다룹니다.
👉 #1화 — 타임박싱이란? 앱 만들게 된 이유
👉 #2화 — PocketBase + Nginx + Proxmox 서버 인프라 구축
👉 #3화 — 멀티유저 인증과 데이터 동기화
👉 #4화 — 전면 리디자인: 오버레이형 통합 레이아웃
👉 #5화 — 모바일 최적화: 터치와의 전쟁
👉 #6화 — PWA: 웹앱을 앱처럼 쓰는 마법
👉 #7화 — 가족끼리 일정 공유
👉 #8화 — 가족끼리 일정 공유
'IT > AI' 카테고리의 다른 글
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #10 — 가족 대시보드, 주간 목표, 알림 (0) | 2026.03.29 |
|---|---|
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #9 — 소셜 시스템 + 인라인 캘린더 (0) | 2026.03.27 |
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #8 — 인프라 보안 & 백업, 그리고 회고 (0) | 2026.03.26 |
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #6 — PWA: 웹앱을 앱처럼 쓰는 마법 🪄 (0) | 2026.03.24 |
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #5 — 모바일 최적화: 터치와의 전쟁 (0) | 2026.03.23 |
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #4 — 전면 리디자인: 오버레이형 통합 레이아웃 (1) | 2026.03.22 |