feat(mail): DB 저장 + 주기 동기화 + 전체 히스토리 수집 + 메일 숨김
build-and-push / build (push) Successful in 33s

- ProjectMailMsg(헤더 저장)·ProjectMailState(동기화 상태) 모델, AutoMigrate 등록
- mailsync.FetchForDomain: nextPageToken 따라 전체 히스토리 페이지네이션(maxPerBox=0=전부)
- 백그라운드 주기 동기화 StartMailSyncLoop(MAIL_SYNC_INTERVAL 기본 15m, 0=비활성)
  · 미동기화 프로젝트=full 백필, 이후=최신 페이지 top-up
- GET /mails는 DB에서 읽어 참여자(from/to/cc) 필터 + 공동 메모 인라인 결합 + lastSyncedAt
- POST /mails/sync(강제 풀싱크), PUT /mail-hide(프로젝트 단위 숨김)
- mailCache 제거(DB가 캐시), config MailSyncInterval

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
theorose49
2026-06-30 12:44:31 +09:00
co-authored by Claude Opus 4.8
parent b4b47e5ed1
commit c865baccd2
8 changed files with 281 additions and 79 deletions
+14
View File
@@ -4,6 +4,7 @@ import (
"net/url"
"os"
"strings"
"time"
)
// Config holds all runtime configuration loaded from environment variables.
@@ -31,6 +32,9 @@ type Config struct {
// JSON with DOMAIN-WIDE DELEGATION (gmail.readonly) used to read project
// client-domain mail. Empty = mail integration disabled.
GoogleSACredentialsFile string
// MailSyncInterval is how often the background mail sync runs (MAIL_SYNC_INTERVAL,
// e.g. "15m"). 0 disables periodic sync (on-demand backfill still works).
MailSyncInterval time.Duration
// LogoutURL is the common SSO logout path injected by infra (LOGOUT_URL):
// ends the oauth2-proxy session then redirects to Keycloak end-session.
// The static frontend reads it via /me. Defaults to the infra-common value.
@@ -48,6 +52,15 @@ func env(key, def string) string {
return def
}
// parseDur parses a Go duration string (e.g. "15m"); invalid/"0" yields 0 (disabled).
func parseDur(s string) time.Duration {
d, err := time.ParseDuration(strings.TrimSpace(s))
if err != nil {
return 0
}
return d
}
// firstEnv returns the first non-empty env var among keys, else def.
func firstEnv(def string, keys ...string) string {
for _, k := range keys {
@@ -83,6 +96,7 @@ func Load() Config {
AdminGroups: splitCSV(env("ADMIN_GROUPS", "admin")),
FCMCredentialsFile: env("FCM_CREDENTIALS_FILE", ""),
GoogleSACredentialsFile: env("GOOGLE_SA_CREDENTIALS_FILE", ""),
MailSyncInterval: parseDur(env("MAIL_SYNC_INTERVAL", "15m")),
LogoutURL: env("LOGOUT_URL",
"/oauth2/sign_out?rd=https%3A%2F%2Fauth.special-partners.com%2Frealms%2Fsp%2Fprotocol%2Fopenid-connect%2Flogout"),
}