Day는 가족 일정과 할 일, 여행 기록을 모아 보는 앱입니다. 이번에는 화면 뒤를 조금 더 들여다봅니다. 어떤 파일이 어떤 일을 맡는지, 변경한 내용이 어디에 저장되는지, 서비스를 고칠 때 무엇을 확인하는지를 README를 읽듯 정리했습니다.
English
Day brings together family schedules, tasks, and travel records. This note looks behind the interface: which files do what, how changes are saved, and what is checked when the service is updated.
01. 먼저, 이 글의 범위
현재 서비스 소스에서 확인한 구조를 기준으로 적었습니다. 디렉터리 트리는 공개에 필요한 부분만 추렸고, 실제 계정·서버 주소·토큰·일정 내용은 넣지 않았습니다. 코드마다 실제 소스에서 가져온 발췌인지, 흐름을 설명하기 위해 줄인 예시인지 구분합니다.
소스에 기능이 있다는 것과 실제 운영에서 끝까지 시험했다는 것은 별개입니다. 특히 외부 동기화와 데이터 복구는 구현 여부, 실행 결과, 남은 확인 사항을 나눠 보려 합니다.
English
The structure described here was checked against the current service source. The tree is selective, and real accounts, server addresses, tokens, and personal records are omitted. Each code sample is labeled as a source excerpt or a shortened illustration. Implemented behavior and end-to-end verified behavior are recorded separately.
02. 서버가 시작되는 순서
서버는 FastAPI와 Uvicorn으로 실행하고 데이터는 SQLite에 저장합니다. 확인한 의존성 파일에는 FastAPI 0.141.1과 Uvicorn 0.52.4가 고정되어 있습니다. 브라우저 코드는 ES modules로 나뉘어 있고, 필요한 데이터를 API에 요청합니다.
start.sh
└─ uvicorn asgi:app
└─ DirectPeerApp
└─ FastAPI
└─ SQLite
실행 입구는 start.sh입니다. Uvicorn이 asgi:app을 불러오고, DirectPeerApp을 거쳐 FastAPI로 요청이 전달됩니다. 코드가 놓인 영역과 쓰기 가능한 데이터 영역은 분리되어 있습니다. 의존성은 파일의 해시를 기준으로 재사용하도록 되어 있어, 배포할 때마다 같은 환경을 처음부터 만들지 않도록 했습니다.
English
The startup chain is start.sh → uvicorn asgi:app → DirectPeerApp → FastAPI. The inspected dependency file pins FastAPI 0.141.1 and Uvicorn 0.52.4. Browser code uses ES modules, and storage uses SQLite. Application code and writable data live in separate areas; dependency environments are reused by dependency-file hash.
03. 파일을 역할별로 읽기
아래는 확인한 파일 중 핵심만 고른 축약 트리입니다. 경로와 릴리스 식별자는 공개용으로 일반화했습니다. deploy.sh와 ops/는 작업 디렉터리의 운영 도구이며, 실행 릴리스에 모두 포함되는 것은 아닙니다. _data/는 공개 배포 대상에서 제외합니다.
day/
├── index.html
├── css/
├── js/
│ ├── app.js # 화면과 사용자 동작
│ ├── store.js # 상태·변경분 저장
│ └── review.js # 규칙 기반 검토
├── server/
│ ├── start.sh
│ ├── requirements.txt
│ ├── asgi.py
│ ├── app.py # API·기본 항목 DB 처리
│ ├── features/
│ ├── family.py
│ ├── gcal.py
│ └── clock_display.py
├── deploy.sh
├── ops/
│ ├── Dockerfile
│ └── day-app-container.sh
├── _public/
│ ├── releases/<release>/
│ └── current -> releases/<release>/
└── _data/ # 비공개 데이터·설정·의존성 캐시
화면 동작을 볼 때는 app.js, 저장 요청을 볼 때는 store.js, 검토 규칙을 볼 때는 review.js부터 읽으면 됩니다. 서버의 기본 항목 API는 app.py에서 SQLite를 직접 처리합니다. 모든 요청이 별도 서비스 계층을 통과하는 구조로 설명하면 실제 코드와 달라집니다.
English
This selective, generalized tree groups the interface, state persistence, review rules, server, and deployment tools. deploy.sh and ops/ belong to the working directory and are not all part of the runtime release. The private data area is excluded from public deployment. Basic item APIs handle SQLite directly in app.py rather than going through a separate service layer.
04. 바뀐 항목이 저장되기까지
브라우저의 store.data 변경
→ 이전 스냅샷과 비교
→ 변경 PUT / 삭제 DELETE를 순서대로 전송
→ API 인증·형식·소유권·공유 범위 확인
→ SQLite BEGIN IMMEDIATE
→ 매개변수 SQL
→ COMMIT
store.save()는 전체 화면 데이터를 매번 통째로 보내는 대신, 이전 스냅샷과 비교해 바뀐 항목과 삭제된 항목의 요청을 만듭니다. 서버는 인증과 입력값, 소유권과 공유 범위를 확인한 뒤 트랜잭션 안에서 저장합니다. 읽기 API인 /api/state에도 가족 가시성 규칙이 적용됩니다.
실제 소스 발췌 · server/app.py의 connect 함수. DB_PATH의 실제 값은 포함하지 않았습니다. sqlite3와 DB_PATH는 주변 모듈에 정의되어 있으므로 이 부분만 독립 실행하는 예시는 아닙니다.
def connect(path=None):
db = sqlite3.connect(path or DB_PATH, timeout=10, isolation_level=None)
db.row_factory = sqlite3.Row
db.execute('pragma journal_mode=wal')
db.execute('pragma foreign_keys=on')
db.execute('pragma busy_timeout=10000')
return db
연결할 때 WAL 모드와 외래 키 검사를 켜고, DB 잠금 대기 시간을 설정합니다. 이 설정이 있다고 모든 요청이 동시에 성공하거나 데이터 복구가 보장되는 것은 아닙니다. 실제 저장은 각 API의 트랜잭션과 오류 처리를 함께 읽어야 합니다.
설명용 축약 · js/store.js의 저장 흐름. 실제 코드를 바탕으로 변수와 구문을 정리한 의사코드이며, 결정 기록 저장 등 세부 처리는 생략했습니다.
변경 요청 목록 = []
각 컬렉션에 대해:
현재 항목을 이전 스냅샷과 비교
새 항목 또는 바뀐 항목 → PUT 요청 추가
사라진 항목 → DELETE 요청 추가
기존 저장 큐 뒤에 이어서:
각 요청을 순서대로 전송
실패하면 오류를 표시하고 서버 상태를 다시 읽기
각 요청이 순서대로 전송된다는 점도 중요합니다. 여러 요청 전체가 하나의 DB 트랜잭션으로 묶였다는 뜻은 아닙니다. 중간에 실패했을 때 화면과 서버를 다시 맞추는 처리까지 저장 기능의 일부로 봅니다.
English
store.save() compares current collections with snapshots and queues changed items as PUT requests and removals as DELETE requests. The API checks authentication, input, ownership, and visibility before SQLite transactions. The connect function above is an unchanged excerpt without the real DB path. The following pseudocode summarizes the client queue and error recovery. Sequential requests do not imply one transaction spanning the whole batch.
05. 검토는 제안을 만들고, 승인을 기다린다
현재 검토는 규칙 기반입니다. 밀린 일, 반복해서 미룬 일, 일정 충돌, 가까워진 마감, 빠진 일지를 대상으로 제안을 만듭니다. 아래는 그중 밀린 할 일을 오늘로 옮기도록 제안하는 부분입니다.
실제 소스 발췌 · js/review.js의 제안 객체 생성 부분. 들여쓰기는 글에 맞춰 정리했습니다. 앞의 할 일 필터와 날짜 차이 계산, 뒤의 다른 규칙은 생략했습니다. t, days, today는 실행 중 계산되는 변수이며 실제 일정이나 사용자 값이 아닙니다. 독립 실행 예시는 아닙니다.
out.push({
id: `overdue:${t.id}:${t.date}`, type: 'overdue', severity: days > 2 ? 3 : 2,
title: `「${t.title}」 ${days}일째 밀려 있어요`,
reason: `예정일 ${shortDate(t.date)} · 지금까지 ${t.postponed}번 미룸`,
change: `${shortDate(t.date)} → ${shortDate(today)} (오늘)`,
action: { op: 'move_task', id: t.id, to: today }, cta: '오늘로 이동', target: { kind: 'task', id: t.id },
});
이 객체에는 왜 제안했는지, 무엇을 바꿀지, 어떤 항목이 대상인지가 함께 들어갑니다. 제안 생성만으로 날짜를 바꾸지는 않습니다. 사용자가 승인하면 applySuggestion()이 화면 데이터와 accepted 결정 기록을 바꾸고, 이어서 저장을 요청합니다. 일정 충돌이나 빠진 일지 관련 제안 중 일부는 편집 화면을 여는 방식입니다.
실제 소스 발췌 · js/app.js의 승인 처리 중 세 줄. 앞의 제안 조회·편집 화면 분기와 바깥 이벤트 핸들러는 생략했습니다.
applySuggestion(D(), s, T());
save();
toast('반영했어요');
여기서 ‘반영했어요’는 저장 요청 직후 표시됩니다. 서버의 커밋 완료를 기다려 확인해 주는 알림으로 읽으면 안 됩니다. 저장이 실패하면 오류를 표시하고 서버 상태를 다시 읽는 처리가 있으므로, 승인 버튼의 반응과 저장 후 재조회 결과를 각각 확인해야 합니다.
English
Review is rule-based. The excerpt constructs an overdue-task suggestion with a reason, proposed change, and target; it contains variables, not real personal records. Approval applies the suggestion to client data, records an accepted decision, and requests persistence. Some suggestions open an editor. The approval toast is shown immediately after requesting a save and does not guarantee a completed server commit; failures trigger an error and a state reload.
06. 변경한 코드를 운영에 올리는 순서
확인한 배포 코드는 테스트를 거친 뒤 index·css·js·server를 별도 릴리스 디렉터리로 복사하고 RELEASE 기록을 남깁니다. 이어서 current 링크를 원자적으로 교체하고 /healthz의 릴리스 응답을 확인합니다. 서버는 current의 실제 경로를 2초마다 살피다가 바뀌면 종료하고, Docker의 unless-stopped 정책이 재시작을 유도합니다.
테스트
→ 별도 릴리스 준비·RELEASE 기록
→ current 링크 원자적 교체
→ 변경 감지 후 프로세스 종료·컨테이너 재시작
→ /healthz 릴리스 응답 확인
문제가 생기면
→ 이전 코드 릴리스로 rollback
전체 시험 도구와 시험용 환경이 없을 때 기본 서버 시험으로 대체하는 분기도 있습니다. 이번 소스 조사에서는 배포 스크립트를 실행하지 않았으므로, 이 순서가 구현되어 있다는 사실을 새 배포의 테스트 통과 결과로 쓰지는 않습니다.
코드를 이전 릴리스로 되돌리는 rollback은 구현되어 있습니다. 다만 이것으로 SQLite 데이터까지 이전 시점으로 돌아가는 것은 아닙니다. 이번에 살펴본 코드에서는 일반적인 DB 자동 백업·복구 절차까지 확인하지 못했습니다. 배포 전 데이터 보존 방법을 따로 확인해야 하는 이유입니다.
English
The inspected deployment flow tests, prepares a separate release, atomically switches the current link, and checks the release reported by /healthz. A server watcher detects a changed release, exits, and lets Docker restart the container. A basic-test fallback exists when full test tooling is unavailable. This inspection did not run a deployment. Code rollback is implemented, but it does not restore SQLite data; a general automatic database backup-and-restore procedure was not confirmed.
07. 운영할 때 확인할 것
구조를 알게 되면 문제가 생겼을 때 확인할 순서도 조금 분명해집니다. 다음은 실제 상태를 단정하는 목록이 아니라, 이 구조에 맞춰 정리한 점검 순서입니다.
다음은 운영자가 자기 환경의 값으로 바꿔 쓰는 읽기 전용 진단 예시입니다. 꺾쇠 안의 값은 모두 자리표시자입니다. 인증을 우회하지 않고, 승인된 로컬 경로에서만 확인합니다.
docker inspect <app-container> --format '{{.State.Status}}'
curl -fsS <operator-approved-local-origin>/healthz
- 실행: 선택된 릴리스와 실행 환경이 맞는지, 프로세스와 health 응답이 정상인지 확인합니다. health 통과만으로 로그인·저장·동기화까지 확인한 것으로 보지는 않습니다.
- 저장: 데이터 영역의 쓰기 권한과 여유 공간을 살피고, 가상 항목을 저장한 뒤 다시 읽어 같은 값이 나오는지 확인합니다. 실제 가족 기록을 테스트용으로 바꾸지 않습니다.
- 검토: 후보 생성, 승인, 결정 기록, 재조회 후 상태를 각각 확인합니다. 화면에서만 바뀌고 서버 저장이 실패한 경우도 따로 시험할 항목입니다.
- 외부 연동: 권한 범위가 있다는 사실과 실제 읽기·쓰기·충돌 처리 결과를 구분합니다. Google 연동의 모든 양방향 동기화 경로가 검증되었다고 쓰지는 않습니다.
- 복구: 이전 코드로 돌아갈 수 있는지와 데이터를 복원할 수 있는지를 별도 항목으로 둡니다. DB 백업이 있다면 복원 시험까지 확인해야 합니다.
로그나 화면을 공유할 때도 코드만 살피면 부족합니다. 요청 본문, 일정 제목, 사용자 식별자, 내부 경로가 남을 수 있으므로 예시는 가상 데이터로 만들고 실제 운영 로그는 그대로 붙이지 않습니다.
English
The operational checklist separates startup and health, storage readback, review approval and persistence, external synchronization, and recovery. These are checks to perform, not claims that all paths passed. A healthy endpoint does not establish that login, writes, or synchronization work, and code rollback does not establish data recovery. Shared logs and screenshots also need a privacy review.
08. 같은 데이터, 다른 화면
Clock도 Day의 데이터를 읽습니다. Clock용 표시 데이터는 공유된 미완료 할 일 가운데 미래 날짜 항목을 걸러 냅니다. 편집 화면에는 다뤄야 할 정보가 많지만, 집 안에 켜 두는 화면에는 지금 볼 범위를 좁히는 규칙이 필요했습니다.
Day를 기술적으로 적어 보니 화면을 만드는 코드만큼 저장 경계와 운영 순서를 남기는 일이 중요했습니다. 다음에 다시 손볼 때도 파일 이름만 찾는 대신, 요청이 어디로 들어오고 어디에 저장되며 어떤 확인을 거쳐 바뀌는지 이어서 읽을 수 있도록 정리해 둡니다.
English
Clock reads Day’s data with a narrower display scope: shared, unfinished tasks, excluding future-dated items. An editing screen and an always-on home display need different views. Recording the storage boundaries and operating sequence makes it easier to follow how a request enters the system, is saved, and is checked when the service changes.
이어서 읽기: 집 안에 켜 둔 Clock · 집 안, 작은 서버
댓글은 현재 WordPress 원문에서 이용할 수 있습니다. 정적 사이트의 댓글 기능은 이전 방식 결정 후 연결합니다.