Skip to content

[FEAT] Phase 1-2: Amplitude Adapter 구현#17

Open
jaeml06 wants to merge 13 commits into
developfrom
feat/#7
Open

[FEAT] Phase 1-2: Amplitude Adapter 구현#17
jaeml06 wants to merge 13 commits into
developfrom
feat/#7

Conversation

@jaeml06
Copy link
Copy Markdown
Contributor

@jaeml06 jaeml06 commented Apr 28, 2026

관련 이슈

closes #7

개요

Phase 1-1에서 잡아둔 골격 위에, 이번 단계에서는 외부 지표를 처음으로 우리 DB에 끌어다 놓는 파이프라인을 만들었습니다. 구체적으로는 Amplitude에서 timer_started 이벤트 발생 횟수를 주간 단위로 가져와 metric_snapshots 테이블에 저장하는 흐름까지입니다.

이번 단계의 핵심은 "Amplitude를 빠르게 붙이는 것" 자체가 아니라, 이후에 Sentry 같은 다른 원천이 추가돼도 기존 코드를 건드리지 않을 수 있는 경계를 먼저 만드는 것이었습니다. Phase 9에서 SentryErrorSourceAdapter가 들어올 때 이 경계가 그대로 재사용되도록 설계 우선순위를 잡았습니다.

전체 흐름은 이렇게 동작합니다:

[Schedule/Manual Trigger]
       │
       ▼
┌──────────────────────┐
│ MetricSourceAdapter  │  ← 인터페이스 (계약)
└──────────────────────┘
       │ implements
       ▼
┌──────────────────────┐    ┌────────────────────┐
│ AmplitudeAdapter     │───▶│ AmplitudeClient    │ ── HTTP ─▶ Amplitude API
│ (도메인 변환 책임)    │    │ (HTTP 호출 책임)    │
└──────────────────────┘    └────────────────────┘
       │
       │ MetricCollectionResult (success | failed)
       ▼
┌──────────────────────┐
│ SnapshotsService     │  ── 성공일 때만 저장 ──▶
└──────────────────────┘
       │
       ▼
┌──────────────────────┐
│ SnapshotsRepository  │ ── upsert ──▶ PostgreSQL (metric_snapshots)
└──────────────────────┘

이 구조에서 한 클래스는 한 가지 일만 합니다. Amplitude 응답 형식이 바뀌어도 저장 코드는 흔들리지 않고, 중복 저장 정책을 바꿔도 Amplitude 코드를 건드릴 필요가 없습니다.


설계 판단

1. MetricSourceAdapter 인터페이스를 먼저 정의했다

가장 빨리 만드는 길은 AmplitudeService 하나에 호출, 변환, 저장을 모두 우겨넣는 것입니다. 그런데 이 프로젝트는 Phase 9에 Sentry 에러 지표를, 그 뒤로도 새 원천을 받아들일 가능성이 큽니다. 그때마다 기존 클래스를 뜯어 고치는 건 위험합니다.

그래서 외부 원천에서 값을 가져오는 행위를 다음과 같은 계약으로 추상화했습니다:

export interface MetricSourceAdapter {
  collect(
    definition: MetricDefinition,
    period: MetricCollectionPeriod,
  ): Promise<MetricCollectionResult>;
}

AmplitudeMetricSourceAdapter는 이 계약의 첫 구현체일 뿐입니다. 새 원천이 들어올 때는 이 인터페이스를 한 번 더 구현하면 됩니다. 호출하는 쪽(이후 Phase 1-3의 RunsService)은 인터페이스만 알면 되기 때문에, 구현이 무엇인지에 대한 의존이 생기지 않습니다.

DI 등록은 Symbol 토큰으로 했습니다:

export const METRIC_SOURCE_ADAPTER = Symbol('METRIC_SOURCE_ADAPTER');

문자열 토큰을 쓰지 않은 이유는 두 가지입니다. 첫째, 문자열은 오타가 나도 컴파일이 통과합니다. 둘째, 다른 모듈에서 같은 문자열을 우연히 쓰면 충돌이 납니다. Symbol은 import해서 쓰는 유일한 참조라 이 두 문제가 사라집니다.


2. 외부 호출과 도메인 변환을 분리했다 (Client ↔ Adapter)

Amplitude 통신을 두 클래스로 쪼갰습니다.

계층 책임 알고 있는 것
AmplitudeClient HTTP 호출 자체 endpoint URL, query string 형식, 인증 헤더, 응답 checksum
AmplitudeMetricSourceAdapter 도메인 규칙 적용 MetricDefinition → API 입력 변환, 결과 → 우리 도메인 결과로 변환

이렇게 나눈 이유는, HTTP 세부사항이 바뀌는 빈도와 도메인 규칙이 바뀌는 빈도가 다르기 때문입니다. Amplitude가 API path를 v3로 바꾸면 Client만 고치면 되고, 우리가 "주간 시작 요일"을 바꾸면 Adapter만 고치면 됩니다.

테스트 관점에서도 효과가 큽니다. fetch 함수 자체를 DI 토큰으로 주입받게 만들었기 때문에:

{ provide: AMPLITUDE_FETCH, useValue: fetch }

테스트에서는 가짜 fetch를 넣어 네트워크 없이도 Client 동작을 검증할 수 있습니다. 실수로 진짜 Amplitude로 요청이 나가는 사고를 구조적으로 막아주는 장치입니다.


3. 결과 타입을 Discriminated Union으로 만들었다

처음에는 실패 시 throw를 던지는 방식도 고려했습니다. 하지만 Phase 1-2의 정책은 명확합니다 — 한 지표가 실패해도 전체 실행은 멈추면 안 됩니다. Phase 1-3 이후 여러 지표를 동시에 돌릴 때, 한 곳의 예외가 다른 지표 수집까지 죽이면 곤란합니다.

그래서 collect()는 예외를 던지지 않고 항상 결과 객체를 반환합니다:

type MetricCollectionResult =
  | { status: 'success'; value: number; rawRef: ...; ... }
  | { status: 'failed'; reasonCode: MetricCollectionFailureCode; message: string; ... };

status 필드 하나로 분기되도록 만들어서 호출자는 이렇게 쓰면 됩니다:

if (result.status === 'success') {
  // TypeScript가 여기서 value, rawRef 등에 접근 가능함을 자동 추론
}

실패 원인은 자유 문자열이 아니라 정해진 코드(UNSUPPORTED_PERIOD_TYPE, UNSUPPORTED_QUERY_SPEC, AMPLITUDE_API_ERROR, AMPLITUDE_RESPONSE_INVALID)로 분류했습니다. 이렇게 하면 나중에 "API 오류는 재시도, querySpec 오류는 알림"처럼 정책을 바꿀 때 코드 매칭으로 분기할 수 있습니다.


4. querySpec을 DB의 JSON 필드로 두고, 런타임에 좁게 검증한다

MetricDefinition.querySpec은 Prisma JSON 필드입니다. DB는 어떤 모양이든 받아주지만, Phase 1-2에서 우리가 실제로 처리할 수 있는 모양은 매우 좁습니다:

{
  version: 1,
  source: 'AMPLITUDE',
  kind: 'EVENT_COUNT',
  eventType: 'timer_started',
  aggregation: 'EVENT_COUNT',
  filters: [],   // 이 단계에서는 필터 없는 전체 집계만
  groupBy: [],   // 그룹핑 없는 전체 세그먼트만
}

이 모양이 아니면 parseAmplitudeEventCountQuerySpec이 던지고, Adapter는 UNSUPPORTED_QUERY_SPEC으로 실패를 반환합니다. "좁게 시작하고, 명세에 합의된 만큼만 확장한다" 원칙을 코드로 박아둔 셈입니다.

이 검증 함수는 unknown 입력에서 시작합니다. DB JSON은 TypeScript가 모양을 보장해주지 않기 때문에, "object인지 → 각 필드가 정확히 그 값인지" 순으로 단계적으로 좁혀나가서 마지막에야 정의된 타입으로 좁힙니다.


5. credential이 새지 않도록 3중 방어선

Amplitude는 Basic Auth(API Key + Secret Key)로 호출합니다. 이 값들이 로그, error message, DB의 rawRef 어디에도 남아서는 안 됩니다. 그래서 보호 장치를 여러 층으로 깔았습니다:

방어선 위치 막는 것
1차 AmplitudeClientsanitizeMessage error message에 섞일 수 있는 secret/Authorization/webhook URL 마스킹
2차 AmplitudeApiError.toJSON() JSON.stringify(error)name, message, status만 노출
3차 SnapshotsRepositoryassertRawRefDoesNotContainSensitiveKeys DB 저장 직전 rawRef를 재귀 순회하며 apiKey/secretKey/authorization 키가 들어있으면 저장 자체를 막음

3차 방어선이 좀 과해 보일 수 있는데, 외부 API 계층의 실수가 DB 영구 저장으로 이어지는 사고를 막기 위한 마지막 안전장치입니다. 한번 DB에 들어간 credential은 회수하기가 정말 어렵습니다.

rawRef에는 응답 본문 대신 sha256:... checksum과 Amplitude의 x-amplitude-request-id만 저장합니다. 추후 디버깅 시 "이 스냅샷이 어떤 응답에서 왔는지"는 추적할 수 있되, 응답 본문 자체는 남기지 않는 절충점입니다.


6. KST 변환 로직을 common/time에 모았다

이 프로젝트는 모든 시간 계산이 Asia/Seoul 기준입니다. 그런데 Date 객체 자체는 UTC 기반이라 변환이 자주 필요합니다. 변환 코드를 여기저기 흩어놓으면 한 군데서 잘못 만지는 순간 데이터가 하루씩 밀립니다.

그래서 formatKstDate, formatAmplitudeEndDateFromExclusiveEnd 두 함수만 common/time/kst-date.ts에 두고, 다른 모듈은 무조건 이 함수들을 통해서만 변환하도록 했습니다.

formatAmplitudeEndDateFromExclusiveEnd가 살짝 흥미로운 부분입니다. 우리 DB는 periodEndexclusive(이 시각은 포함 안 함)로 저장하는데, Amplitude의 end 파라미터는 inclusive입니다. 그래서 1ms를 빼서 "마지막 포함 날짜"를 만들어 넘깁니다. 이걸 빼먹으면 주간 경계에서 하루가 어긋납니다.


7. metric_snapshotsupsert로 저장한다

스케줄러가 같은 주에 두 번 돌거나, 수동으로 재실행할 때 같은 스냅샷이 중복으로 들어가면 안 됩니다. 그래서 저장은 항상 compound unique key 기준의 upsert입니다:

where: {
  metricDefinitionId_periodType_periodKey_segmentKey_segmentValue: { ... },
},
update: {},   // 이미 있으면 그대로 둠
create: { ... }

update: {}로 둔 건 의도적입니다. 이미 저장된 스냅샷의 값은 절대 덮어쓰지 않습니다. Amplitude 응답이 시간이 지나면서 미세하게 흔들릴 수 있는데, 한 번 기록한 값은 그 시점의 fact로 유지하는 게 비교·분석의 일관성을 위해 더 안전하다고 판단했습니다. 만약 정말로 다시 받아야 한다면 명시적인 재수집 기능을 따로 만들 예정입니다.


8. TZ=Asia/Seoul 환경변수를 필수로 검증한다

코드 레벨에서 KST를 잘 다루더라도, Node 프로세스 자체의 TZ가 다르면 Date 출력이나 pino 로그 타임스탬프가 어긋납니다. 이건 잡기 정말 어려운 종류의 버그라서, ConfigSchema에 다음 한 줄을 추가했습니다:

TZ: Joi.string().valid('Asia/Seoul').required(),

다른 값이면 앱이 부팅조차 안 됩니다. "운영자가 컨테이너에 TZ를 잊어먹어서 KST가 아닌 채로 한 주가 흘러갔다"는 사고를 부팅 시점에 막는 장치입니다.


추가로 정비한 것

코드 외에 학습 아카이브 운영 방식도 이번 PR에 함께 넣었습니다 (docs/learning/). 학습 노트를 모든 대화마다 남기는 게 아니라, 면접/포트폴리오/후속 구현에서 다시 설명할 가치가 있을 때만 정리하는 흐름으로 정했습니다. 이번 단계에서 뽑은 두 개의 노트:

  • architecture/2026-04-27-layered-boundaries.md — Adapter ↔ Repository 경계 분리의 이유
  • testing/2026-04-28-tdd-external-api-isolation.md — 외부 API를 DI fetch로 격리해 테스트하는 방법

specs/는 로컬 작업 산출물이므로 .gitignore에 추가했습니다.


브랜치 타입

  • feat/ – 신규 기능
  • fix/ – 버그 수정
  • hotfix/ – 프로덕션 긴급 수정
  • refactor/ – 기능 변경 없는 코드 개선
  • docs/ – 문서 작성·수정
  • test/ – 테스트 추가·수정
  • chore/ – 의존성·설정·도구 관련
  • ci/ – GitHub Actions·CI/CD 설정

체크리스트

  • PR 제목이 [TYPE] 작업 제목 형식인가?
  • 브랜치 이름이 {type}/#{issue_number} 형식인가?
  • 대상 브랜치가 올바른가? (develop)
  • 테스트를 추가하거나 기존 테스트가 통과하는가?
  • 관련 문서를 업데이트했는가?

Summary by CodeRabbit

  • Documentation

    • 학습 아카이브(목록·가이드·구조도) 및 Phase 1-2 Amplitude Adapter 관련 사양·계획·테스트 계약 문서 대량 추가
  • New Features

    • Amplitude 연동 메트릭 수집(클라이언트·어댑터), 스냅샷 저장 서비스/레포지토리, DI 모듈 연동 및 KST 날짜 유틸리티 추가
    • 구성 검증에 TZ=Asia/Seoul 요구 추가
  • Tests

    • 클라이언트/어댑터/레포지토리/서비스 및 유틸리티 대상 단위 테스트 추가
  • Chores

    • .gitignore 규칙 확장 및 Node 엔진 제약 추가

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

Walkthrough

Amplitude 기반 메트릭 수집 기능(Phase 1–2)을 추가합니다. KST 날짜 유틸리티와 config 검증(TZ=Asia/Seoul), MetricSourceAdapter 타입·토큰, Amplitude HTTP 클라이언트(요청/타임아웃/체크섬/민감값 필터), Amplitude 어댑터(주간만 지원), 스냅샷 저장소·서비스(재귀 민감키 검증, upsert) 및 관련 테스트·문서·모듈 연결이 포함됩니다.

Changes

Amplitude Adapter (Phase 1-2)

Layer / File(s) Summary
Data Shape / Types
src/modules/metric-sources/core/metric-collection.types.ts, specs/.../data-model.md
메트릭 수집 입력/출력 타입(기간, success/failure, MetricSnapshotRawRef 등) 및 관련 스펙 문서화 추가.
Core Contracts / DI Token
src/modules/metric-sources/core/metric-source-adapter.ts, src/modules/metric-sources/core/metric-source.tokens.ts, specs/.../contracts/metric-source-adapter.md
MetricSourceAdapter 인터페이스와 METRIC_SOURCE_ADAPTER 토큰 선언 및 계약 문서 추가.
Amplitude QuerySpec Validation
src/modules/metric-sources/amplitude/amplitude-query-spec.ts, src/modules/metric-sources/amplitude/amplitude-query-spec.spec.ts
Phase1-2 고정 AmplitudeEventCountQuerySpec 타입 및 런타임 파서, 파서 유닛테스트 추가.
KST Time Utilities
src/common/time/kst-date.ts, src/common/time/kst-date.spec.ts, specs/.../test-contracts/kst-date.md
KST YYYYMMDD 변환 및 exclusive-end → Amplitude end-date 유틸리티와 테스트/계약 추가.
Amplitude HTTP Client
src/modules/metric-sources/amplitude/amplitude.client.ts, src/modules/metric-sources/amplitude/amplitude.client.spec.ts, specs/.../test-contracts/amplitude-client.md
DI 가능한 fetch 토큰(AMPLITUDE_FETCH), Basic Auth 요청, 15s 타임아웃(AbortController), 응답 텍스트 기반 SHA-256 체크섬, 오류/메시지 정제(AmplitudeApiError) 구현 및 광범위한 테스트·계약 추가.
Amplitude Adapter Implementation
src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.ts, src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.spec.ts, specs/.../test-contracts/amplitude-adapter.md
AmplitudeMetricSourceAdapter.collect() 추가: WEEKLY만 지원, querySpec 파싱, 클라이언트 호출(start/end KST 포맷), 응답 검증(빈 결과→0, malformed→AMPLITUDE_RESPONSE_INVALID), API 오류→AMPLITUDE_API_ERROR 매핑 및 관련 테스트 추가.
Snapshots Repository & Service
src/modules/snapshots/snapshots.repository.ts, src/modules/snapshots/snapshots.repository.spec.ts, src/modules/snapshots/snapshots.service.ts, src/modules/snapshots/snapshots.service.spec.ts, specs/.../contracts/snapshot-persistence.md
SnapshotsRepository.upsertCollectionSuccess() 추가: rawRef 재귀 민감키 검사(정규화 및 프래그먼트 검사), Prisma metricSnapshot.upsert 호출(복합키, update: {}), SnapshotsService.saveCollectionResult()와 테스트/계약 추가.
Module Wiring / App Integration
src/modules/metric-sources/metric-sources.module.ts, src/modules/snapshots/snapshots.module.ts, src/app.module.ts
MetricSourcesModule(AMPLITUDE_FETCH provider, AmplitudeClient, AmplitudeMetricSourceAdapter 바인딩)와 SnapshotsModule을 구현하고 AppModule에 등록.
Config Schema
src/modules/config/config.schema.ts, src/modules/config/config.schema.spec.ts
환경 변수 TZ를 필수(Asia/Seoul)로 추가하고 관련 유닛테스트 추가.
Specs / Docs / Tasks
specs/feat/007-amplitude-adapter/**, docs/learning/**, docs/README.md, docs/learning-and-portfolio.md
Amplitude Adapter 계획·스펙·테스트 계약·연구·작업 목록 및 학습 아카이브(인덱스/운영 규칙) 문서 대거 추가.
Repo config / housekeeping
.gitignore, package.json
.gitignore에 `specs/**/*.(local
sequenceDiagram
    participant Caller
    participant Adapter as AmplitudeMetricSourceAdapter
    participant Client as AmplitudeClient
    participant Amplitude as Amplitude API
    participant Service as SnapshotsService
    participant Repository as SnapshotsRepository
    participant Database as Prisma/DB

    Caller->>Adapter: collect(definition, period)
    alt period type not WEEKLY
        Adapter-->>Caller: MetricCollectionFailure(UNSUPPORTED_PERIOD_TYPE)
    else
        Adapter->>Adapter: parseAmplitudeEventCountQuerySpec()
        alt parse fails
            Adapter-->>Caller: MetricCollectionFailure(UNSUPPORTED_QUERY_SPEC)
        else
            Adapter->>Client: fetchEventSegmentation(startDate, endDate)
            alt API error
                Client-->>Adapter: AmplitudeApiError (sanitized)
                Adapter-->>Caller: MetricCollectionFailure(AMPLITUDE_API_ERROR)
            else success
                Client->>Client: compute SHA-256 checksum
                Client-->>Adapter: {body, rawRef}
                Adapter->>Adapter: extract value from seriesCollapsed
                alt invalid response
                    Adapter-->>Caller: MetricCollectionFailure(AMPLITUDE_RESPONSE_INVALID)
                else valid
                    Adapter-->>Caller: MetricCollectionSuccess
                end
            end
        end
    end

    Caller->>Service: saveCollectionResult(result)
    alt result.status === 'failed'
        Service-->>Caller: {status: 'skipped', reason: 'COLLECTION_FAILED'}
    else status === 'success'
        Service->>Repository: upsertCollectionSuccess(result)
        Repository->>Repository: validateRawRef (scan for sensitive keys)
        alt sensitive key found
            Repository-->>Service: Error
            Service-->>Caller: Exception
        else no sensitive data
            Repository->>Database: metricSnapshot.upsert(...)
            Database-->>Repository: MetricSnapshot
            Repository-->>Service: MetricSnapshot
            Service-->>Caller: {status: 'saved', snapshot}
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

"나는 토끼, 키보드 위를 hoppity-hoppity"
"KST로 날짜를 재고, 체크섬도 까먹지 않지"
"Amplitude는 WEEKLY만 받으며"
"원천 참조는 비밀을 지키고"
"스냅샷은 안전히 DB에 남기네" 🐇✨


개요

이 변경사항은 Amplitude 외부 API에서 메트릭을 수집하고, 수집 결과를 데이터베이스에 저장하는 메트릭 소스 어댑터 아키텍처를 구현합니다. KST 기반 날짜 처리, 구성 스키마 확장, 핵심 타입 정의, Amplitude 클라이언트 및 어댑터, 그리고 스냅샷 저장소/서비스를 포함합니다.

변경사항

Cohort / File(s) 요약
Git 설정
.gitignore
specs/ 디렉터리를 버전 관리에서 제외하도록 무시 패턴 추가
설정 및 시간 유틸리티
src/modules/config/config.schema.ts, src/modules/config/config.schema.spec.ts, src/common/time/kst-date.ts, src/common/time/kst-date.spec.ts
TZ 환경 변수(Asia/Seoul)를 필수로 검증. KST 날짜 포맷팅 유틸리티 추가하여 UTC 타임스탬프를 YYYYMMDD 문자열로 변환
메트릭 소스 핵심 인터페이스
src/modules/metric-sources/core/metric-collection.types.ts, src/modules/metric-sources/core/metric-source-adapter.ts, src/modules/metric-sources/core/metric-source.tokens.ts
메트릭 수집의 주기, 성공/실패 결과, 원본 참조(rawRef) 타입 정의. MetricSourceAdapter 인터페이스 및 의존성 주입 토큰 추가
Amplitude 어댑터 구현
src/modules/metric-sources/amplitude/amplitude-query-spec.ts, src/modules/metric-sources/amplitude/amplitude-query-spec.spec.ts, src/modules/metric-sources/amplitude/amplitude.client.ts, src/modules/metric-sources/amplitude/amplitude.client.spec.ts, src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.ts, src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.spec.ts
Amplitude API 통합 구현: querySpec 파서, 클라이언트(fetch 주입, SHA-256 체크섬, 민감정보 필터링), 메트릭 수집 어댑터(WEEKLY만 지원, 실패 처리). 전체 기능을 테스트로 고정
메트릭 스냅샷 저장
src/modules/snapshots/snapshots.repository.ts, src/modules/snapshots/snapshots.repository.spec.ts, src/modules/snapshots/snapshots.service.ts, src/modules/snapshots/snapshots.service.spec.ts, src/modules/snapshots/snapshots.module.ts
성공한 메트릭 수집 결과를 Prisma로 upsert하는 저장소. rawRef 민감정보 재귀 검증. 결과별 저장/스킵 처리 서비스
모듈 연결
src/app.module.ts, src/modules/metric-sources/metric-sources.module.ts
MetricSourcesModule(Amplitude 클라이언트/어댑터 DI 구성)과 SnapshotsModule을 AppModule에 import
학습 문서
docs/README.md, docs/learning-and-portfolio.md, docs/learning/README.md, docs/learning/architecture/2026-04-27-layered-boundaries.md, docs/learning/testing/2026-04-28-tdd-external-api-isolation.md
학습 아카이브 진입점 인덱싱, 아카이브 운영 흐름 및 문서화 기준 정립. 계층 경계 분리 설계 및 TDD 기반 외부 API 격리 테스트 설계 문서화

시퀀스 다이어그램

sequenceDiagram
    participant Caller
    participant Adapter as AmplitudeMetricSourceAdapter
    participant Client as AmplitudeClient
    participant Amplitude as Amplitude API
    participant Service as SnapshotsService
    participant Repository as SnapshotsRepository
    participant Database as Prisma/DB

    Caller->>Adapter: collect(definition, period)
    alt period type not WEEKLY
        Adapter-->>Caller: MetricCollectionFailure(UNSUPPORTED_PERIOD_TYPE)
    else
        Adapter->>Adapter: parseAmplitudeEventCountQuerySpec()
        alt parse fails
            Adapter-->>Caller: MetricCollectionFailure(UNSUPPORTED_QUERY_SPEC)
        else
            Adapter->>Client: fetchEventSegmentation(startDate, endDate)
            alt API error
                Client-->>Adapter: AmplitudeApiError (sanitized)
                Adapter-->>Caller: MetricCollectionFailure(AMPLITUDE_API_ERROR)
            else success
                Client->>Client: compute SHA-256 checksum
                Client-->>Adapter: {body, rawRef}
                Adapter->>Adapter: extract value from seriesCollapsed
                alt invalid response
                    Adapter-->>Caller: MetricCollectionFailure(AMPLITUDE_RESPONSE_INVALID)
                else valid
                    Adapter-->>Caller: MetricCollectionSuccess
                end
            end
        end
    end

    Caller->>Service: saveCollectionResult(result)
    alt result.status === 'failed'
        Service-->>Caller: {status: 'skipped', reason: 'COLLECTION_FAILED'}
    else status === 'success'
        Service->>Repository: upsertCollectionSuccess(result)
        Repository->>Repository: validateRawRef (scan for sensitive keys)
        alt sensitive key found
            Repository-->>Service: Error
            Service-->>Caller: Exception
        else no sensitive data
            Repository->>Database: metricSnapshot.upsert(...)
            Database-->>Repository: MetricSnapshot
            Repository-->>Service: MetricSnapshot
            Service-->>Caller: {status: 'saved', snapshot}
        end
    end
Loading

예상 코드 리뷰 노력

🎯 4 (복잡함) | ⏱️ ~45분

🐰 계층 사이 경계를 긋고
외부 API 호출을 격리하니
테스트도 깔끔하고 확장도 쉽구나
민감 정보는 철저히 걸러내고
Amplitude는 WEEKLY만 받는다!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 변경의 핵심을 명확히 전달하며 [FEAT] 형식을 따르고 Phase 1-2 Amplitude Adapter 구현이라는 주요 변경사항을 정확히 설명합니다.
Description check ✅ Passed PR 설명은 관련 이슈, 개요, 설계 판단 8가지, 추가 정비 사항을 포함하여 템플릿의 모든 필수 섹션을 충족하고 상세한 맥락을 제공합니다.
Linked Issues check ✅ Passed 코드 변경사항이 이슈 #7의 모든 주요 요구사항을 충족합니다: MetricSourceAdapter 인터페이스 정의, AmplitudeMetricSourceAdapter 구현, 지표별 실패 격리, Discriminated Union 결과 타입 설계.
Out of Scope Changes check ✅ Passed 모든 변경사항이 Phase 1-2 Amplitude Adapter 구현 범위 내에 있습니다: KST 유틸, config 검증, 인증 분리, 스냅샷 저장, 학습 문서, gitignore 정비 등이 모두 관련 범위입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#7

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (4)
src/modules/metric-sources/core/metric-source-adapter.ts (1)

1-13: 코어 계약에서 Prisma 타입 의존을 분리하는 것을 권장합니다.

core 레이어 인터페이스가 generated/prisma 타입을 직접 참조하면, 소스 어댑터 계약이 영속성 구현 세부사항에 결합됩니다. 코어 전용 입력 타입(예: 최소 필드 DTO)을 두고 매핑을 호출부에서 처리하면 향후 소스/저장소 교체가 더 쉬워집니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/metric-sources/core/metric-source-adapter.ts` around lines 1 -
13, The MetricSourceAdapter interface currently depends on the Prisma-generated
MetricDefinition type, coupling core contracts to persistence details; create a
core DTO (e.g., MetricDefinitionCore or MetricDefinitionDTO) that contains only
the minimal fields needed by adapters, change the collect signature on
MetricSourceAdapter.collect to accept that DTO instead of MetricDefinition, and
update adapter implementations and callers to map between generated/prisma
MetricDefinition and the new core DTO at the application boundary (e.g., in
repository/adapter factory code) so the core layer no longer imports
generated/prisma types.
src/modules/metric-sources/amplitude/amplitude-query-spec.spec.ts (1)

14-59: eventType 비허용값 거부 케이스도 테스트에 고정해두는 것을 권장합니다.

현재 케이스들은 충분히 좋지만, Phase 범위(timer_started만 허용)를 테스트로 명시하면 파서 완화 회귀를 더 확실히 막을 수 있습니다.

테스트 보강 예시
 describe('Amplitude querySpec 검증', () => {
@@
   test('filters 또는 groupBy가 비어 있지 않으면 거부한다', () => {
@@
   });
+
+  test('timer_started가 아닌 eventType은 거부한다', () => {
+    expect(() =>
+      parseAmplitudeEventCountQuerySpec({
+        ...validQuerySpec,
+        eventType: 'session_start',
+      }),
+    ).toThrow('Unsupported Amplitude querySpec');
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/metric-sources/amplitude/amplitude-query-spec.spec.ts` around
lines 14 - 59, Add a negative test that explicitly rejects disallowed eventType
values to prevent parser relaxation: in the amplitude-query-spec.spec.ts tests
for parseAmplitudeEventCountQuerySpec (using validQuerySpec), add an assertion
that passing an eventType other than 'timer_started' (e.g., 'timer_ended' or '')
throws 'Unsupported Amplitude querySpec'. This uses the existing test pattern
(expect(() => parseAmplitudeEventCountQuerySpec({...validQuerySpec, eventType:
'...'})).toThrow(...)) so the parser remains strict about only allowing the
'timer_started' eventType.
src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.spec.ts (1)

141-154: “형식 오류” 테스트에 구조 손상 케이스도 추가하는 것을 권장합니다.

현재 Line 141-154는 value 타입 오류만 다룹니다. seriesCollapsed가 배열이 아닌 경우까지 포함하면, 응답 스키마 변경 시 0으로 오인 처리되는 회귀를 더 잘 막을 수 있습니다.

🧪 제안 테스트 케이스
+  test('seriesCollapsed 구조가 비정상이면 실패 처리한다', async () => {
+    const client = createClientMock({
+      body: { data: { seriesCollapsed: 'not-an-array' } },
+      rawRef,
+    } as unknown as AmplitudeSegmentationResult);
+    const adapter = new AmplitudeMetricSourceAdapter(client);
+
+    const result = await adapter.collect(definition, period);
+
+    expect(result).toMatchObject({
+      status: 'failed',
+      reasonCode: 'AMPLITUDE_RESPONSE_INVALID',
+    });
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.spec.ts`
around lines 141 - 154, Add a new unit test to the existing suite that asserts
AmplitudeMetricSourceAdapter.collect returns a failed result with reasonCode
'AMPLITUDE_RESPONSE_INVALID' when the Amplitude response's seriesCollapsed is
not an array (e.g., seriesCollapsed: null or a plain object); use the same test
setup utilities (createClientMock, AmplitudeSegmentationResult, rawRef,
definition, period) and the AmplitudeMetricSourceAdapter.collect call to produce
the invalid response scenario and assert the expected failure, mirroring the
existing malformed-value test to cover the structural corruption case.
src/modules/metric-sources/core/metric-collection.types.ts (1)

14-24: core 계약이 이미 Amplitude 전용으로 굳어져 있습니다.

MetricSnapshotRawRef, MetricCollectionSuccess, MetricCollectionFailure'AMPLITUDE'와 단일 endpoint literal을 직접 들고 있어서 다음 source를 추가할 때 adapter만 붙이는 대신 core 타입/저장 계층까지 같이 수정하게 됩니다. MetricSource 별칭과 source별 rawRef union(or generic)으로 한 단계 분리해 두면, PR 목표였던 “새 원천 추가 시 기존 코드 변경 최소화”에 더 잘 맞습니다.

Also applies to: 35-38, 74-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/metric-sources/core/metric-collection.types.ts` around lines 14 -
24, The core types are hard-coded to Amplitude (MetricSnapshotRawRef,
MetricCollectionSuccess, MetricCollectionFailure) which forces core changes for
new sources; introduce a MetricSource alias/union (e.g., 'AMPLITUDE' | 'OTHER')
and refactor MetricSnapshotRawRef into a generic or discriminated union keyed by
MetricSource so source and endpoint literals move into per-source adapters
(e.g., MetricSnapshotRawRef<T extends MetricSource> or a RawRefMap union), then
update MetricCollectionSuccess and MetricCollectionFailure to reference the
generic/discriminated raw-ref instead of fixed literals so adding a new source
only requires adding a new per-source raw-ref type and adapter without changing
core storage types; apply the same pattern to the other analogous types that
currently embed AMPLITUDE literals.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.gitignore:
- Line 19: 현재 .gitignore에 있는 "specs/" 전체 제외는 범위가 과도하므로 "specs/" 항목을 제거하고 대신 로컬
전용 또는 임시 파일만 좁게 무시하도록 패턴화하세요; 예를 들어 "specs/" 전체 대신 특정 패턴 (예: specs/*.local,
specs/*.tmp, specs/*.bak, specs/*.swp 등)만 추가하여 문서(명세) 파일이 버전 관리에서 누락되지 않도록 하세요 —
즉 .gitignore에서 "specs/" 항목을 삭제하고 필요한 로컬/임시 파일 패턴으로 대체하십시오.

In `@src/common/time/kst-date.ts`:
- Around line 10-27: Add input validation to guard against invalid Date objects:
in formatKstDate(date: Date) check if date.getTime() is NaN and throw a
TypeError (with a clear message referencing formatKstDate), and similarly
validate periodEnd in formatAmplitudeEndDateFromExclusiveEnd(periodEnd: Date)
(or call a small shared validateDate helper used by both). This prevents
producing bogus strings when date inputs are invalid; keep existing logic using
KST_OFFSET_MILLISECONDS and ONE_MILLISECOND unchanged.

In `@src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.ts`:
- Around line 135-143: The current guards incorrectly treat malformed responses
as valid zero values; update the checks around seriesCollapsed and firstSeries
so that when !isUnknownArray(seriesCollapsed) or seriesCollapsed.length === 0,
and when !isUnknownArray(firstSeries) or firstSeries.length === 0, the function
returns { isValid: false } (and omit or set value to undefined) instead of {
isValid: true, value: 0 }; adjust the code paths using this function to skip
snapshot/aggregation on isValid === false and optionally add a
processLogger.warn (or similar) noting the broken Amplitude response for easier
debugging.

In `@src/modules/metric-sources/amplitude/amplitude-query-spec.ts`:
- Around line 34-47: The current isSupported check only validates required field
values but allows extra keys on querySpec; update the validation in
amplitude-query-spec.ts to enforce an exact shape by verifying
Object.keys(querySpec) (or a helper like validateExactKeys) matches the allowed
set for AMPLITUDE EVENT_COUNT queries (e.g.,
['version','source','kind','eventType','aggregation','filters','groupBy']), and
reject if any unexpected keys exist before using isSupported; alternatively use
a small JSON-schema/strict-parse helper to ensure no extra properties are
present when evaluating querySpec.

In `@src/modules/metric-sources/amplitude/amplitude.client.ts`:
- Around line 123-135: The checksum is computed from JSON.stringify(body) after
calling response.json(), which loses original byte fidelity and skips JSON parse
errors; change the logic in the function that builds the
AmplitudeSegmentationResponse/rawRef so you first call response.text() to
capture the rawResponseText, compute the sha256 over that raw text using
createHash, then attempt JSON.parse(rawResponseText) inside a try/catch and on
parse failure throw or rethrow an AmplitudeApiError with context (include
response.headers.get('x-amplitude-request-id') if present); ensure the returned
body is the parsed AmplitudeSegmentationResponse and rawRef.responseChecksum
uses the hash of rawResponseText rather than JSON.stringify(body).
- Around line 94-102: The fetch in amplitude.client.ts (inside the block using
this.amplitudeFetch) lacks a timeout and can hang; wrap the call with an
AbortController, pass controller.signal into the this.amplitudeFetch options,
start a setTimeout that calls controller.abort() after a chosen timeout (e.g.,
configurable DEFAULT_AMPLITUDE_TIMEOUT), and clear the timer when the request
completes; in the catch path detect abort errors (error.name === 'AbortError' or
similar) and rethrow an AmplitudeApiError so aborts are converted to
AmplitudeApiError; ensure the timer is always cleared (finally) to avoid leaks
and make the timeout value configurable via the class or method options.

In `@src/modules/metric-sources/metric-sources.module.ts`:
- Around line 9-11: The provider registering AMPLITUDE_FETCH currently uses
useValue: fetch which will crash on Node <18; update the provider so it
guarantees a runtime-safe fetch instead of assuming global fetch: either add an
engines entry ("node": ">=18") to package.json, add and initialize a polyfill
(e.g., install and import node-fetch or cross-fetch and use that), or change the
provider implementation to use a runtime fallback (e.g., use globalThis.fetch if
defined otherwise require/import a node-fetch fallback) so AMPLITUDE_FETCH
always receives a valid function.

In `@src/modules/snapshots/snapshots.repository.ts`:
- Around line 49-81: The current sensitive key set in
assertRawRefDoesNotContainSensitiveKeys/containsSensitiveKey is too narrow (only
'apikey','secretkey','authorization') and misses variants like api_key,
accessToken, refresh-token, password, etc.; update the detection to normalize
each object key (lowercase and strip non-alphanumeric characters or convert
camelCase to delimiter form) and then match against a broader list and/or
patterns (e.g., tokens: 'token', 'access', 'refresh'; keys:
'api','key','secret'; auth/password variants:
'auth','authorization','password','pass','cred') or use substring/regex matching
rather than exact equality so keys like api_key, apiKey, refresh-token,
accessToken are caught; apply this logic inside containsSensitiveKey when
iterating Object.entries and keep throwing the same Error in
assertRawRefDoesNotContainSensitiveKeys when a match is found.

---

Nitpick comments:
In
`@src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.spec.ts`:
- Around line 141-154: Add a new unit test to the existing suite that asserts
AmplitudeMetricSourceAdapter.collect returns a failed result with reasonCode
'AMPLITUDE_RESPONSE_INVALID' when the Amplitude response's seriesCollapsed is
not an array (e.g., seriesCollapsed: null or a plain object); use the same test
setup utilities (createClientMock, AmplitudeSegmentationResult, rawRef,
definition, period) and the AmplitudeMetricSourceAdapter.collect call to produce
the invalid response scenario and assert the expected failure, mirroring the
existing malformed-value test to cover the structural corruption case.

In `@src/modules/metric-sources/amplitude/amplitude-query-spec.spec.ts`:
- Around line 14-59: Add a negative test that explicitly rejects disallowed
eventType values to prevent parser relaxation: in the
amplitude-query-spec.spec.ts tests for parseAmplitudeEventCountQuerySpec (using
validQuerySpec), add an assertion that passing an eventType other than
'timer_started' (e.g., 'timer_ended' or '') throws 'Unsupported Amplitude
querySpec'. This uses the existing test pattern (expect(() =>
parseAmplitudeEventCountQuerySpec({...validQuerySpec, eventType:
'...'})).toThrow(...)) so the parser remains strict about only allowing the
'timer_started' eventType.

In `@src/modules/metric-sources/core/metric-collection.types.ts`:
- Around line 14-24: The core types are hard-coded to Amplitude
(MetricSnapshotRawRef, MetricCollectionSuccess, MetricCollectionFailure) which
forces core changes for new sources; introduce a MetricSource alias/union (e.g.,
'AMPLITUDE' | 'OTHER') and refactor MetricSnapshotRawRef into a generic or
discriminated union keyed by MetricSource so source and endpoint literals move
into per-source adapters (e.g., MetricSnapshotRawRef<T extends MetricSource> or
a RawRefMap union), then update MetricCollectionSuccess and
MetricCollectionFailure to reference the generic/discriminated raw-ref instead
of fixed literals so adding a new source only requires adding a new per-source
raw-ref type and adapter without changing core storage types; apply the same
pattern to the other analogous types that currently embed AMPLITUDE literals.

In `@src/modules/metric-sources/core/metric-source-adapter.ts`:
- Around line 1-13: The MetricSourceAdapter interface currently depends on the
Prisma-generated MetricDefinition type, coupling core contracts to persistence
details; create a core DTO (e.g., MetricDefinitionCore or MetricDefinitionDTO)
that contains only the minimal fields needed by adapters, change the collect
signature on MetricSourceAdapter.collect to accept that DTO instead of
MetricDefinition, and update adapter implementations and callers to map between
generated/prisma MetricDefinition and the new core DTO at the application
boundary (e.g., in repository/adapter factory code) so the core layer no longer
imports generated/prisma types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f58a197-b6a3-41cd-86ed-020b40e70675

📥 Commits

Reviewing files that changed from the base of the PR and between 909ad0b and df78f06.

📒 Files selected for processing (26)
  • .gitignore
  • docs/README.md
  • docs/learning-and-portfolio.md
  • docs/learning/README.md
  • docs/learning/architecture/2026-04-27-layered-boundaries.md
  • docs/learning/testing/2026-04-28-tdd-external-api-isolation.md
  • src/app.module.ts
  • src/common/time/kst-date.spec.ts
  • src/common/time/kst-date.ts
  • src/modules/config/config.schema.spec.ts
  • src/modules/config/config.schema.ts
  • src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.spec.ts
  • src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.ts
  • src/modules/metric-sources/amplitude/amplitude-query-spec.spec.ts
  • src/modules/metric-sources/amplitude/amplitude-query-spec.ts
  • src/modules/metric-sources/amplitude/amplitude.client.spec.ts
  • src/modules/metric-sources/amplitude/amplitude.client.ts
  • src/modules/metric-sources/core/metric-collection.types.ts
  • src/modules/metric-sources/core/metric-source-adapter.ts
  • src/modules/metric-sources/core/metric-source.tokens.ts
  • src/modules/metric-sources/metric-sources.module.ts
  • src/modules/snapshots/snapshots.module.ts
  • src/modules/snapshots/snapshots.repository.spec.ts
  • src/modules/snapshots/snapshots.repository.ts
  • src/modules/snapshots/snapshots.service.spec.ts
  • src/modules/snapshots/snapshots.service.ts

Comment thread .gitignore Outdated
Comment thread src/common/time/kst-date.ts
Comment thread src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.ts Outdated
Comment thread src/modules/metric-sources/amplitude/amplitude-query-spec.ts
Comment thread src/modules/metric-sources/amplitude/amplitude.client.ts Outdated
Comment thread src/modules/metric-sources/amplitude/amplitude.client.ts Outdated
Comment thread src/modules/metric-sources/metric-sources.module.ts
Comment thread src/modules/snapshots/snapshots.repository.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
specs/feat/007-amplitude-adapter/spec.md (1)

64-65: ⚡ Quick win

KST 경계 처리에 대한 “고정 예시”를 요구사항/성공기준에 1개 추가해 주세요.

현재 문구만으로도 의도는 맞지만, UTC↔KST 경계(주간 시작/종료, exclusive end)를 숫자 예시 1건으로 고정하면 테스트 해석 차이를 줄일 수 있습니다. 예: periodEnd=2026-04-19T15:00:00.000Z일 때 Amplitude end date가 20260419로 계산되는지 등.

Also applies to: 112-113

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/feat/007-amplitude-adapter/spec.md` around lines 64 - 65, 요구사항의 KST 경계
해석을 명확히 하기 위해 한 건의 고정 예시를 추가하세요: 예를 들어 periodEnd=2026-04-19T15:00:00.000Z(UTC)일
때 시스템은 KST 기준 종결일을 사용해 Amplitude end date를 20260419로 계산한다는 문구를 성공기준에 삽입하고(즉,
UTC로 저장되는 시작·종료 시각은 그대로 유지하되 해석은 KST이며 end는 exclusive 규칙을 적용), 동일한 예시를 명세의 대응
섹션(현재 112-113에 해당하는 위치)에도 복제하여 테스트 해석 차이를 없애세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@specs/feat/007-amplitude-adapter/spec.md`:
- Around line 64-65: 요구사항의 KST 경계 해석을 명확히 하기 위해 한 건의 고정 예시를 추가하세요: 예를 들어
periodEnd=2026-04-19T15:00:00.000Z(UTC)일 때 시스템은 KST 기준 종결일을 사용해 Amplitude end
date를 20260419로 계산한다는 문구를 성공기준에 삽입하고(즉, UTC로 저장되는 시작·종료 시각은 그대로 유지하되 해석은 KST이며
end는 exclusive 규칙을 적용), 동일한 예시를 명세의 대응 섹션(현재 112-113에 해당하는 위치)에도 복제하여 테스트 해석 차이를
없애세요.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0287d4cb-dea6-41a0-853e-55bcf1de4bba

📥 Commits

Reviewing files that changed from the base of the PR and between df78f06 and 84ea722.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (24)
  • .gitignore
  • package.json
  • specs/feat/007-amplitude-adapter/checklists/requirements.md
  • specs/feat/007-amplitude-adapter/contracts/metric-source-adapter.md
  • specs/feat/007-amplitude-adapter/contracts/snapshot-persistence.md
  • specs/feat/007-amplitude-adapter/data-model.md
  • specs/feat/007-amplitude-adapter/plan.md
  • specs/feat/007-amplitude-adapter/research.md
  • specs/feat/007-amplitude-adapter/spec.md
  • specs/feat/007-amplitude-adapter/tasks.md
  • specs/feat/007-amplitude-adapter/test-contracts/amplitude-adapter.md
  • specs/feat/007-amplitude-adapter/test-contracts/amplitude-client.md
  • specs/feat/007-amplitude-adapter/test-contracts/kst-date.md
  • specs/feat/007-amplitude-adapter/test-contracts/snapshots.md
  • src/common/time/kst-date.spec.ts
  • src/common/time/kst-date.ts
  • src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.spec.ts
  • src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.ts
  • src/modules/metric-sources/amplitude/amplitude-query-spec.spec.ts
  • src/modules/metric-sources/amplitude/amplitude-query-spec.ts
  • src/modules/metric-sources/amplitude/amplitude.client.spec.ts
  • src/modules/metric-sources/amplitude/amplitude.client.ts
  • src/modules/snapshots/snapshots.repository.spec.ts
  • src/modules/snapshots/snapshots.repository.ts
✅ Files skipped from review due to trivial changes (15)
  • package.json
  • specs/feat/007-amplitude-adapter/research.md
  • src/common/time/kst-date.ts
  • .gitignore
  • specs/feat/007-amplitude-adapter/test-contracts/kst-date.md
  • specs/feat/007-amplitude-adapter/data-model.md
  • specs/feat/007-amplitude-adapter/plan.md
  • specs/feat/007-amplitude-adapter/test-contracts/amplitude-adapter.md
  • src/common/time/kst-date.spec.ts
  • specs/feat/007-amplitude-adapter/tasks.md
  • specs/feat/007-amplitude-adapter/checklists/requirements.md
  • specs/feat/007-amplitude-adapter/test-contracts/snapshots.md
  • specs/feat/007-amplitude-adapter/contracts/snapshot-persistence.md
  • specs/feat/007-amplitude-adapter/contracts/metric-source-adapter.md
  • src/modules/metric-sources/amplitude/amplitude-metric-source.adapter.spec.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/modules/snapshots/snapshots.repository.spec.ts
  • src/modules/metric-sources/amplitude/amplitude-query-spec.spec.ts
  • src/modules/metric-sources/amplitude/amplitude-query-spec.ts
  • src/modules/snapshots/snapshots.repository.ts
  • src/modules/metric-sources/amplitude/amplitude.client.spec.ts
  • src/modules/metric-sources/amplitude/amplitude.client.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 1-2: Amplitude Adapter 구현

1 participant