Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e3853b9
feat: 로그인·재발급 응답에 expiresInSeconds 추가
chanwoo7 Oct 5, 2026
7a89fc2
feat: 판매자·관리자 주문 요약에 첫 품목명·썸네일 스냅샷 추가
chanwoo7 Oct 5, 2026
2b002ff
Merge pull request #497 from CaQuick/feat/auth-login-expires-in
chanwoo7 Oct 5, 2026
ee3a18b
feat: 판매자 대화 읽음 마커·미읽음 수·구매자 닉네임 스냅샷
chanwoo7 Oct 5, 2026
e802ac7
feat: 판매자 모바일용 refresh 토큰 바디 전달 경로
chanwoo7 Oct 5, 2026
e6fb8f0
Merge pull request #499 from CaQuick/feat/conversation-seller-read-ma…
chanwoo7 Oct 5, 2026
a808a14
Merge pull request #500 from CaQuick/feat/auth-seller-refresh-body-tr…
chanwoo7 Oct 5, 2026
6ee1340
feat: 판매자 홈 집계 sellerDashboard 추가
chanwoo7 Oct 5, 2026
8a19c87
Merge pull request #502 from CaQuick/feat/seller-dashboard-summary
chanwoo7 Oct 5, 2026
66fa12f
feat: 판매자 내 계정 조회 sellerMe 추가
chanwoo7 Oct 5, 2026
a7adf59
test: roles-coverage 개수 재계산
chanwoo7 Oct 5, 2026
8d5524a
Merge pull request #498 from CaQuick/feat/seller-order-list-first-item
chanwoo7 Oct 5, 2026
29a4325
feat: 판매자 태그 검색 sellerSearchTags 추가
chanwoo7 Oct 5, 2026
9fe88bf
test: roles-coverage 개수 재계산
chanwoo7 Oct 5, 2026
2f631e7
feat: 푸시 원천 outbox 이벤트 order.submitted·conversation.buyer_message_sent
chanwoo7 Oct 5, 2026
2cd35e0
Merge pull request #504 from CaQuick/feat/auth-seller-me
chanwoo7 Oct 5, 2026
8c83696
Merge pull request #505 from CaQuick/feat/seller-tag-search
chanwoo7 Oct 5, 2026
8d898b7
feat: 판매자·관리자 로그인 rate limit
chanwoo7 Oct 5, 2026
898f6c7
test: roles-coverage 개수 재계산
chanwoo7 Oct 5, 2026
0ecdbce
Merge pull request #507 from CaQuick/feat/push-source-outbox-events
chanwoo7 Oct 5, 2026
29f896e
Merge pull request #509 from CaQuick/feat/auth-login-rate-limit
chanwoo7 Oct 5, 2026
ede3353
feat: 판매자 푸시 디바이스 토큰 등록·해제
chanwoo7 Oct 5, 2026
ec36075
fix: 푸시 토큰 바이너리 collation·형식 검사 서비스 이관
chanwoo7 Oct 5, 2026
98b5670
test: roles-coverage 개수 재계산
chanwoo7 Oct 5, 2026
2db7e7e
Merge pull request #510 from CaQuick/feat/seller-push-device
chanwoo7 Oct 5, 2026
9b8e4b4
feat: 판매자 이름 기반 상품 태그 설정 sellerSetProductTagsByName 추가
chanwoo7 Oct 5, 2026
f42680f
fix: 태그 이름 정규화 뒤 길이 재검사
chanwoo7 Oct 5, 2026
0304acb
fix: 태그 upsert 잠금 순서 고정
chanwoo7 Oct 5, 2026
327a305
test: roles-coverage 개수 재계산
chanwoo7 Oct 5, 2026
1a869ba
feat: 판매자 주문 갱신 구독 sellerOrderUpdated 추가
chanwoo7 Oct 5, 2026
6e2ce77
test: roles-coverage 개수 재계산
chanwoo7 Oct 5, 2026
1a35175
fix: 같은 상품 태그 교체를 상품 행 잠금으로 직렬화
chanwoo7 Oct 5, 2026
a31476b
Merge pull request #511 from CaQuick/feat/seller-order-updated-subscr…
chanwoo7 Oct 5, 2026
aebe584
Merge pull request #508 from CaQuick/feat/seller-product-tags-by-name
chanwoo7 Oct 5, 2026
fd1586a
feat: 판매자 푸시 발송 소비자(Expo Push Service)·영수증 스케줄러
chanwoo7 Oct 5, 2026
32520f4
fix: Expo 푸시 전송 타임아웃 abort·디바이스 비활성 순서
chanwoo7 Oct 5, 2026
3a7d013
test: roles-coverage 개수 재계산
chanwoo7 Oct 5, 2026
918cb6f
Merge pull request #512 from CaQuick/feat/seller-push-consumer
chanwoo7 Oct 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ APP_ROLE=worker PORT=4001 yarn start:dev # (선택) 이벤트 소비까지 보
| **문서 · 메트릭** | `DOCS_ACCESS_TOKEN`(`/gql-docs`와 `/rest-docs` 접근 토큰, 운영 필수), `METRICS_ACCESS_TOKEN`(`/metrics` Bearer 토큰, 운영 필수) |
| **경보 (선택)** | `DISCORD_ALERT_WEBHOOK_URL`(없으면 로그로만 남깁니다), `ALERT_DEDUPE_WINDOW_MS`(기본 5분), `BOOT_ALERT_STATE_DIR`(부팅 실패 경보의 억제 파일 위치, 기본 `~/.caquick/boot-alert`) |
| **Outbox (선택)** | `OUTBOX_DISPATCH_ENABLED`(`false`로 끄는 용도만 있으며 켜는 것은 worker 역할이 결정), `OUTBOX_POLL_INTERVAL_MS`, `OUTBOX_BATCH_SIZE`, `OUTBOX_MAX_ATTEMPTS`, `OUTBOX_PARTITION_CONCURRENCY` |
| **푸시 (선택)** | `EXPO_PUSH_ENABLED`(`true`일 때만 worker가 판매자 앱 푸시를 보내고, 기본 `false`면 소비자가 전송 없이 ack합니다), `EXPO_PUSH_ACCESS_TOKEN`(Expo 액세스 토큰, 없으면 인증 헤더 없이 보냅니다), `EXPO_PUSH_TIMEOUT_MS`(기본 5000) |
| **시드 (선택)** | `ADMIN_SEED_USERNAME`, `ADMIN_SEED_PASSWORD`, `SELLER_SEED_PASSWORD` |

### 자주 쓰는 스크립트
Expand Down
5 changes: 5 additions & 0 deletions infra/app.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,8 @@ DISCORD_ALERT_WEBHOOK_URL=
ALERT_DEDUPE_WINDOW_MS=
OUTBOX_MAX_ATTEMPTS=
OUTBOX_PARTITION_CONCURRENCY=

# ---- 앱: 판매자 푸시(Expo Push Service) ----
EXPO_PUSH_ENABLED= # true일 때만 worker가 전송한다(기본 false — 소비자가 전송 없이 ack)
EXPO_PUSH_ACCESS_TOKEN=
EXPO_PUSH_TIMEOUT_MS=
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- 판매자 읽음 마커(명시 mutation·답장이 전진)와 구매자 닉네임 표시 스냅샷. 마커는 NULL = "읽은 적 없음"으로 시작한다.
-- AlterTable
ALTER TABLE `store_conversation` ADD COLUMN `buyer_nickname_snapshot` VARCHAR(50) NULL,
ADD COLUMN `seller_last_read_at` DATETIME(3) NULL;

-- backfill: 생성 시점 값은 복원할 수 없어 현재 활성 프로필의 닉네임으로 채운다. 탈퇴 프로필(deleted_at)은 조인에서 빠져 NULL로 남는다.
UPDATE `store_conversation` c
JOIN `user_profile` up ON up.account_id = c.account_id AND up.deleted_at IS NULL
SET c.buyer_nickname_snapshot = NULLIF(up.nickname, '');
-- end backfill
43 changes: 43 additions & 0 deletions prisma/migrations/20261005145157_seller_push_device/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- 판매자 앱 Expo 푸시 디바이스(토큰 unique, 소유 계정은 마지막 등록자)와 이벤트×디바이스 전달 추적 행. 계정·매장 FK 없음, 활성 판정은 disabled_at IS NULL.
-- expo_push_token은 utf8mb4_bin: 대소문자만 다른 토큰이 같은 디바이스로 합쳐져 소유권이 넘어가지 않게.

-- CreateTable
CREATE TABLE `seller_push_device` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`account_id` BIGINT UNSIGNED NOT NULL,
`store_id` BIGINT UNSIGNED NOT NULL,
`expo_push_token` VARCHAR(200) COLLATE utf8mb4_bin NOT NULL,
`platform` ENUM('IOS', 'ANDROID') NOT NULL,
`client_device_id` VARCHAR(128) NULL,
`last_seen_at` DATETIME(3) NOT NULL,
`disabled_at` DATETIME(3) NULL,
`disabled_reason` VARCHAR(32) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL,

INDEX `idx_seller_push_device_store_active`(`store_id`, `disabled_at`),
INDEX `idx_seller_push_device_account`(`account_id`),
UNIQUE INDEX `uk_seller_push_device_token`(`expo_push_token`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `seller_push_delivery` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`source_event_id` CHAR(36) NOT NULL,
`push_device_id` BIGINT UNSIGNED NOT NULL,
`status` ENUM('PENDING', 'TICKET_OK', 'TICKET_ERROR', 'RECEIPT_OK', 'RECEIPT_ERROR', 'RECEIPT_UNKNOWN') NOT NULL DEFAULT 'PENDING',
`ticket_id` VARCHAR(64) NULL,
`error_code` VARCHAR(64) NULL,
`sent_at` DATETIME(3) NULL,
`receipt_checked_at` DATETIME(3) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

INDEX `idx_seller_push_delivery_receipt`(`status`, `sent_at`),
UNIQUE INDEX `uk_seller_push_delivery_event_device`(`source_event_id`, `push_device_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `seller_push_delivery` ADD CONSTRAINT `seller_push_delivery_push_device_id_fkey` FOREIGN KEY (`push_device_id`) REFERENCES `seller_push_device`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

72 changes: 71 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ enum NotificationEvent {
ORDER_CANCELED
}

enum PushPlatform {
IOS
ANDROID
}

// Expo 푸시 전달 단계. send 응답(ticket) → 영수증(receipt) 순으로 전진한다.
enum SellerPushDeliveryStatus {
PENDING
TICKET_OK
TICKET_ERROR
RECEIPT_OK
RECEIPT_ERROR
RECEIPT_UNKNOWN
}

enum AuditTargetType {
STORE
PRODUCT
Expand Down Expand Up @@ -1157,6 +1172,57 @@ model NotificationBroadcast {
@@map("notification_broadcast")
}

// 판매자 앱 푸시 디바이스. 토큰이 기기·설치 단위 식별자라 토큰 unique, 소유 계정은 마지막 등록자다.
// 계정·매장 FK는 두지 않는다(감사·이력 성격, NotificationBroadcast와 같은 선택). 활성 판정은 disabled_at IS NULL.
model SellerPushDevice {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
account_id BigInt @db.UnsignedBigInt
// 등록 시점 판매자 매장. 소비자가 이벤트 storeId로 바로 찾는다 — Store 조인 없음.
store_id BigInt @db.UnsignedBigInt

/// 바이너리 collation(utf8mb4_bin) — 마이그레이션 SQL 참조
expo_push_token String @db.VarChar(200)
platform PushPlatform
// 앱이 부여한 기기 식별자(표시·추적용). 서버 로직은 토큰만 본다.
client_device_id String? @db.VarChar(128)

last_seen_at DateTime @db.DateTime(3)
disabled_at DateTime? @db.DateTime(3)
// UNREGISTERED | DEVICE_NOT_REGISTERED
disabled_reason String? @db.VarChar(32)

created_at DateTime @default(now()) @db.DateTime(3)
updated_at DateTime @updatedAt @db.DateTime(3)

deliveries SellerPushDelivery[]

@@unique([expo_push_token], map: "uk_seller_push_device_token")
@@index([store_id, disabled_at], map: "idx_seller_push_device_store_active")
@@index([account_id], map: "idx_seller_push_device_account")
@@map("seller_push_device")
}

// outbox 이벤트 1건 × 디바이스 1개 = 1행. at-least-once 재전달 dedupe 키이자 ticket → receipt 추적 행.
model SellerPushDelivery {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
source_event_id String @db.Char(36)
push_device_id BigInt @db.UnsignedBigInt

status SellerPushDeliveryStatus @default(PENDING)
ticket_id String? @db.VarChar(64)
error_code String? @db.VarChar(64)

sent_at DateTime? @db.DateTime(3)
receipt_checked_at DateTime? @db.DateTime(3)
created_at DateTime @default(now()) @db.DateTime(3)

device SellerPushDevice @relation(fields: [push_device_id], references: [id])

@@unique([source_event_id, push_device_id], map: "uk_seller_push_delivery_event_device")
@@index([status, sent_at], map: "idx_seller_push_delivery_receipt")
@@map("seller_push_delivery")
}

/**
* =========================
* 11) Search
Expand Down Expand Up @@ -1348,7 +1414,11 @@ model StoreConversation {
store_id BigInt @db.UnsignedBigInt

last_message_at DateTime? @db.DateTime(3)
last_read_at DateTime? @db.DateTime(3)
// 구매자 읽음 마커(조회 시 전진). 판매자 마커는 명시 mutation·답장이 전진시킨다.
last_read_at DateTime? @db.DateTime(3)
seller_last_read_at DateTime? @db.DateTime(3)
// 대화 생성 시점 구매자 닉네임(표시용 스냅샷). 도입 전 대화는 백필로 못 채우면 null.
buyer_nickname_snapshot String? @db.VarChar(50)

created_at DateTime @default(now()) @db.DateTime(3)
updated_at DateTime @updatedAt @db.DateTime(3)
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import appConfig, {
import authConfig from '@/config/auth.config';
import databaseConfig from '@/config/database.config';
import docsConfig from '@/config/docs.config';
import expoPushConfig from '@/config/expo-push.config';
import kakaoLocalConfig from '@/config/kakao-local.config';
import metricsConfig from '@/config/metrics.config';
import oidcConfig from '@/config/oidc.config';
Expand Down Expand Up @@ -124,6 +125,7 @@ export class AppModule implements NestModule {
authConfig,
databaseConfig,
docsConfig,
expoPushConfig,
kakaoLocalConfig,
metricsConfig,
oidcConfig,
Expand Down
2 changes: 2 additions & 0 deletions src/common/errors/error-catalog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ const RENDER_PARAMS: Partial<
IDS_LENGTH_MISMATCH: { field: 'imageIds' },
INVALID_IDS: { field: 'imageIds' },
PRODUCT_IMAGE_LIMIT_EXCEEDED: { max: 10 },
PRODUCT_TAG_LIMIT_EXCEEDED: { max: 20 },
UNSUPPORTED_OIDC_PROVIDER: { provider: 'facebook' },
LOGIN_RATE_LIMITED: { minutes: 15 },
};

// 필터 밖 경로(Apollo가 리졸버 진입 전에 만드는 에러)는 카탈로그 코드가 아니라 Apollo 자체 code를 낸다:
Expand Down
13 changes: 13 additions & 0 deletions src/common/errors/error-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export const ERROR_CATALOG = {
status: HttpStatus.TOO_MANY_REQUESTS,
message: '요청이 너무 많습니다. 잠시 후 다시 시도해 주세요.',
},
LOGIN_RATE_LIMITED: {
status: HttpStatus.TOO_MANY_REQUESTS,
message: ({ minutes }) =>
`로그인 시도가 너무 많습니다. ${minutes}분 뒤 다시 시도해 주세요.`,
},
ROUTE_NOT_FOUND: {
status: HttpStatus.NOT_FOUND,
message: '요청한 경로를 찾을 수 없습니다.',
Expand Down Expand Up @@ -212,6 +217,10 @@ export const ERROR_CATALOG = {
status: HttpStatus.NOT_FOUND,
message: '알림을 찾을 수 없습니다.',
},
INVALID_PUSH_TOKEN: {
status: HttpStatus.BAD_REQUEST,
message: '푸시 토큰 형식이 올바르지 않습니다.',
},

// ── 카탈로그(매장·상품·지역)
STORE_NOT_FOUND: {
Expand Down Expand Up @@ -391,6 +400,10 @@ export const ERROR_CATALOG = {
status: HttpStatus.BAD_REQUEST,
message: ({ max }) => `상품 이미지는 최대 ${max}장까지 등록할 수 있습니다.`,
},
PRODUCT_TAG_LIMIT_EXCEEDED: {
status: HttpStatus.BAD_REQUEST,
message: ({ max }) => `태그는 최대 ${max}개까지 등록할 수 있습니다.`,
},
PRODUCT_IMAGE_MIN_REQUIRED: {
status: HttpStatus.BAD_REQUEST,
message: '상품 이미지는 1장 이상 필요합니다.',
Expand Down
51 changes: 51 additions & 0 deletions src/config/expo-push.config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { readExpoPushConfig } from '@/config/expo-push.config';

describe('expoPushConfig', () => {
it('미설정이면 꺼짐·토큰 없음·타임아웃 5000', () => {
expect(readExpoPushConfig({})).toEqual({
enabled: false,
accessToken: null,
requestTimeoutMs: 5_000,
});
});

// 전송 스위치 — true/false(대소문자·공백 무시)만 인정하고 나머지는 꺼짐
it.each([
['true', true],
['TRUE', true],
[' true ', true],
['false', false],
['', false],
['yes', false],
['1', false],
['on', false],
[undefined, false],
])('EXPO_PUSH_ENABLED=%p → %s', (value, expected) => {
expect(readExpoPushConfig({ EXPO_PUSH_ENABLED: value }).enabled).toBe(
expected,
);
});

it.each([
[' tok ', 'tok'],
['', null],
[' ', null],
[undefined, null],
])('EXPO_PUSH_ACCESS_TOKEN=%p → %p', (value, expected) => {
expect(
readExpoPushConfig({ EXPO_PUSH_ACCESS_TOKEN: value }).accessToken,
).toBe(expected);
});

it.each([
['250', 250],
['0', 5_000],
['-1', 5_000],
['abc', 5_000],
['', 5_000],
])('EXPO_PUSH_TIMEOUT_MS=%p → %s', (value, expected) => {
expect(
readExpoPushConfig({ EXPO_PUSH_TIMEOUT_MS: value }).requestTimeoutMs,
).toBe(expected);
});
});
35 changes: 35 additions & 0 deletions src/config/expo-push.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { registerAs } from '@nestjs/config';

import {
parseEnvBoolean,
parseEnvNumber,
parseEnvString,
} from '@/common/utils/env-parse';

export interface ExpoPushConfig {
/** 꺼져 있으면 소비자가 전송 없이 ack만 한다(이력도 남기지 않는다). 기본 false. */
enabled: boolean;
/** Expo 액세스 토큰(선택). 있으면 Authorization: Bearer. */
accessToken: string | null;
requestTimeoutMs: number;
}

const DEFAULT_TIMEOUT_MS = 5_000;

/** 선택 설정 — 부팅을 막는 조건이 없다. 비표준 값은 기본값으로 떨어진다. */
export function readExpoPushConfig(
env: NodeJS.ProcessEnv = process.env,
): ExpoPushConfig {
return {
enabled: parseEnvBoolean(env.EXPO_PUSH_ENABLED, false),
accessToken: parseEnvString(env.EXPO_PUSH_ACCESS_TOKEN) ?? null,
requestTimeoutMs: parseEnvNumber(
env.EXPO_PUSH_TIMEOUT_MS,
DEFAULT_TIMEOUT_MS,
),
};
}

export default registerAs('expoPush', (): ExpoPushConfig =>
readExpoPushConfig(),
);
37 changes: 37 additions & 0 deletions src/features/auth/auth-seller-account.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
extend type Query {
"""
내 판매자 계정을 조회한다. 판매자 로그인 필수. SELLER 계정이 아니면 FORBIDDEN.
초기 비밀번호 상태(mustChangePassword=true)면 다른 판매자 API와 같이 FORBIDDEN이므로, 초기 비밀번호 여부는 refresh 응답의 mustChangePassword로 판단한다.
"""
sellerMe: SellerAccount!
}

"""
판매자 본인 계정. 로그인 자격증명·계정 상태·보유 매장 ID를 함께 보여준다. 매장명은 sellerMyStore로 받는다.
"""
type SellerAccount {
"""
계정 ID.
"""
accountId: ID!
"""
로그인 username. 자격증명이 없는 개발용 계정이면 null.
"""
username: String
"""
표시 이름. 계정 이름이 있으면 그것, 없으면 사업자명, 둘 다 없으면 null.
"""
displayName: String
"""
보유 매장 ID. 매장이 없거나 삭제됐으면 null.
"""
storeId: ID
"""
초기 비밀번호 상태. true면 본인이 비밀번호를 바꾸기 전까지 판매자 API가 FORBIDDEN이다.
"""
mustChangePassword: Boolean!
"""
계정 상태.
"""
accountStatus: AccountStatus!
}
5 changes: 5 additions & 0 deletions src/features/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import { AdminAccountMutationResolver } from '@/features/auth/resolvers/auth-adm
import { AdminAccountQueryResolver } from '@/features/auth/resolvers/auth-admin-account-query.resolver';
import { AdminUserMutationResolver } from '@/features/auth/resolvers/auth-admin-user-mutation.resolver';
import { AdminUserQueryResolver } from '@/features/auth/resolvers/auth-admin-user-query.resolver';
import { SellerAccountQueryResolver } from '@/features/auth/resolvers/auth-seller-account-query.resolver';
import { UserProfileMutationResolver } from '@/features/auth/resolvers/auth-user-profile-mutation.resolver';
import { UserProfileQueryResolver } from '@/features/auth/resolvers/auth-user-profile-query.resolver';
import { AdminAccountService } from '@/features/auth/services/auth-admin-account.service';
import { AdminUserService } from '@/features/auth/services/auth-admin-user.service';
import { SellerAccountService } from '@/features/auth/services/auth-seller-account.service';
import { UserProfileService } from '@/features/auth/services/auth-user-profile.service';
import { BlacklistRebuildService } from '@/features/auth/services/blacklist-rebuild.service';
import { CredentialAuthService } from '@/features/auth/services/credential-auth.service';
Expand Down Expand Up @@ -63,6 +65,9 @@ import { AuthGlobalModule } from '@/global/auth/auth-global.module';
AdminAccountMutationResolver,
AdminUserQueryResolver,
AdminUserMutationResolver,
// 판매자 본인 계정(sellerMe) — identity가 소유한다
SellerAccountService,
SellerAccountQueryResolver,
// 구매자 계정·프로필(me·온보딩·수정·탈퇴) — identity가 소유한다
AccountUserRepository,
UserProfileService,
Expand Down
Loading
Loading