create-entity-server 0.0.9 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/bin/create.js +11 -1
  2. package/package.json +1 -1
  3. package/template/.env.example +17 -1
  4. package/template/configs/jwt.json +1 -0
  5. package/template/configs/oauth.json +37 -0
  6. package/template/configs/push.json +26 -0
  7. package/template/entities/README.md +4 -4
  8. package/template/entities/{Auth → System/Auth}/account.json +0 -14
  9. package/template/samples/README.md +16 -0
  10. package/template/samples/entities/01_basic_fields.json +39 -0
  11. package/template/samples/entities/02_types_and_defaults.json +68 -0
  12. package/template/samples/entities/03_hash_and_unique.json +33 -0
  13. package/template/samples/entities/04_fk_and_composite_unique.json +31 -0
  14. package/template/samples/entities/05_cache.json +54 -0
  15. package/template/samples/entities/06_history_and_hard_delete.json +42 -0
  16. package/template/samples/entities/07_license_scope.json +43 -0
  17. package/template/samples/entities/08_hook_sql.json +52 -0
  18. package/template/samples/entities/09_hook_entity.json +71 -0
  19. package/template/samples/entities/10_hook_submit_delete.json +75 -0
  20. package/template/samples/entities/11_hook_webhook.json +82 -0
  21. package/template/samples/entities/12_hook_push.json +73 -0
  22. package/template/samples/entities/13_read_only.json +51 -0
  23. package/template/samples/entities/14_optimistic_lock.json +29 -0
  24. package/template/samples/entities/15_reset_defaults.json +95 -0
  25. package/template/samples/entities/README.md +94 -0
  26. package/template/samples/entities/order_notification.json +51 -0
  27. package/template/samples/flutter/lib/entity_server_client.dart +91 -0
  28. package/template/samples/java/EntityServerClient.java +117 -0
  29. package/template/samples/kotlin/EntityServerClient.kt +86 -0
  30. package/template/samples/node/src/EntityServerClient.js +116 -0
  31. package/template/samples/php/ci4/Config/EntityServer.php +15 -0
  32. package/template/samples/php/ci4/Controllers/EntityController.php +202 -0
  33. package/template/samples/php/ci4/Controllers/ProductController.php +16 -76
  34. package/template/samples/php/ci4/Libraries/EntityServer.php +150 -11
  35. package/template/samples/php/laravel/Services/EntityServerService.php +56 -0
  36. package/template/samples/python/entity_server.py +106 -0
  37. package/template/samples/react/src/api/entityServerClient.ts +123 -0
  38. package/template/samples/react/src/hooks/useEntity.ts +68 -0
  39. package/template/samples/swift/EntityServerClient.swift +105 -0
  40. package/template/scripts/normalize-entities.sh +10 -10
  41. package/template/scripts/run.sh +108 -29
  42. package/template/scripts/update-server.ps1 +92 -2
  43. package/template/scripts/update-server.sh +73 -2
  44. /package/template/entities/{Auth → System/Auth}/api_keys.json +0 -0
  45. /package/template/entities/{Auth → System/Auth}/license.json +0 -0
  46. /package/template/entities/{Auth → System/Auth}/rbac_roles.json +0 -0
@@ -2,6 +2,8 @@
2
2
 
3
3
  namespace App\Libraries;
4
4
 
5
+ use Config\EntityServer as EntityServerConfig;
6
+
5
7
  /**
6
8
  * Entity Server 클라이언트 라이브러리 (CodeIgniter 4)
7
9
  *
@@ -9,7 +11,7 @@ namespace App\Libraries;
9
11
  *
10
12
  * 설치: app/Libraries/EntityServer.php 에 배치
11
13
  *
12
- * 설정: app/Config/EntityServer.php 또는 .env 에서
14
+ * 설정: app/Config/EntityServer.php 우선 (필요시 .env fallback)
13
15
  * ENTITY_SERVER_URL=http://localhost:47200
14
16
  * ENTITY_SERVER_API_KEY=your-api-key
15
17
  * ENTITY_SERVER_HMAC_SECRET=your-hmac-secret
@@ -28,20 +30,32 @@ class EntityServer
28
30
  private string $hmacSecret;
29
31
  private int $timeout;
30
32
  private int $magicLen;
33
+ private bool $requireEncryptedRequest;
31
34
  private ?string $activeTxId = null;
32
35
 
33
36
  public function __construct(
34
- string $baseUrl = '',
35
- string $apiKey = '',
36
- string $hmacSecret = '',
37
- int $timeout = 10,
38
- int $magicLen = 4
37
+ ?string $baseUrl = null,
38
+ ?string $apiKey = null,
39
+ ?string $hmacSecret = null,
40
+ ?int $timeout = null,
41
+ ?int $magicLen = null,
42
+ ?bool $requireEncryptedRequest = null
39
43
  ) {
40
- $this->baseUrl = rtrim($baseUrl ?: env('ENTITY_SERVER_URL', 'http://localhost:47200'), '/');
41
- $this->apiKey = $apiKey ?: env('ENTITY_SERVER_API_KEY', '');
42
- $this->hmacSecret = $hmacSecret ?: env('ENTITY_SERVER_HMAC_SECRET', '');
43
- $this->timeout = $timeout;
44
- $this->magicLen = (int) ($magicLen ?: env('ENTITY_PACKET_MAGIC_LEN', 4));
44
+ $config = class_exists(EntityServerConfig::class) ? new EntityServerConfig() : null;
45
+
46
+ $configBaseUrl = $config?->baseUrl ?? env('ENTITY_SERVER_URL', 'http://localhost:47200');
47
+ $configApiKey = $config?->apiKey ?? env('ENTITY_SERVER_API_KEY', '');
48
+ $configHmacSecret = $config?->hmacSecret ?? env('ENTITY_SERVER_HMAC_SECRET', '');
49
+ $configTimeout = $config?->timeout ?? (int) env('ENTITY_SERVER_TIMEOUT', 10);
50
+ $configMagicLen = $config?->magicLen ?? (int) env('ENTITY_PACKET_MAGIC_LEN', 4);
51
+ $configRequireEncrypted = $config?->requireEncryptedRequest ?? true;
52
+
53
+ $this->baseUrl = rtrim($baseUrl ?? (string) $configBaseUrl, '/');
54
+ $this->apiKey = (string) ($apiKey ?? $configApiKey);
55
+ $this->hmacSecret = (string) ($hmacSecret ?? $configHmacSecret);
56
+ $this->timeout = (int) ($timeout ?? $configTimeout);
57
+ $this->magicLen = (int) ($magicLen ?? $configMagicLen);
58
+ $this->requireEncryptedRequest = (bool) ($requireEncryptedRequest ?? $configRequireEncrypted);
45
59
  }
46
60
 
47
61
  // ─── CRUD ────────────────────────────────────────────────────────────────
@@ -155,6 +169,131 @@ class EntityServer
155
169
  return $this->request('POST', "/v1/entity/{$entity}/rollback/{$historySeq}");
156
170
  }
157
171
 
172
+ /**
173
+ * 푸시 발송 트리거 엔티티에 submit합니다.
174
+ */
175
+ public function push(string $pushEntity, array $payload, ?string $transactionId = null): array
176
+ {
177
+ return $this->submit($pushEntity, $payload, $transactionId);
178
+ }
179
+
180
+ /**
181
+ * push_log 목록 조회 헬퍼
182
+ */
183
+ public function pushLogList(array $params = []): array
184
+ {
185
+ return $this->list('push_log', $params);
186
+ }
187
+
188
+ /**
189
+ * 디바이스 등록/갱신 헬퍼 (push_token 단일 필드)
190
+ *
191
+ * - 기본 대상 엔티티: account_device
192
+ * - 신규 등록: seq 미전달
193
+ * - 기존 레코드 갱신: options['seq'] 전달
194
+ *
195
+ * @param int $accountSeq 계정 seq
196
+ * @param string $deviceId 디바이스 고유 ID (account_device.id)
197
+ * @param string $pushToken 푸시 디바이스 토큰
198
+ * @param array $options 추가 필드 (예: platform, device_type, browser, push_enabled, seq)
199
+ * @param string|null $transactionId transStart()로 얻은 트랜잭션 ID
200
+ */
201
+ public function registerPushDevice(
202
+ int $accountSeq,
203
+ string $deviceId,
204
+ string $pushToken,
205
+ array $options = [],
206
+ ?string $transactionId = null
207
+ ): array {
208
+ $payload = array_merge([
209
+ 'id' => $deviceId,
210
+ 'account_seq' => $accountSeq,
211
+ 'push_token' => $pushToken,
212
+ 'push_enabled' => true,
213
+ ], $options);
214
+
215
+ return $this->submit('account_device', $payload, $transactionId);
216
+ }
217
+
218
+ /**
219
+ * account_device.seq 기준으로 push_token 갱신
220
+ */
221
+ public function updatePushDeviceToken(
222
+ int $deviceSeq,
223
+ string $pushToken,
224
+ bool $pushEnabled = true,
225
+ ?string $transactionId = null
226
+ ): array {
227
+ return $this->submit('account_device', [
228
+ 'seq' => $deviceSeq,
229
+ 'push_token' => $pushToken,
230
+ 'push_enabled' => $pushEnabled,
231
+ ], $transactionId);
232
+ }
233
+
234
+ /**
235
+ * account_device.seq 기준으로 푸시 수신 비활성화
236
+ */
237
+ public function disablePushDevice(
238
+ int $deviceSeq,
239
+ ?string $transactionId = null
240
+ ): array {
241
+ return $this->submit('account_device', [
242
+ 'seq' => $deviceSeq,
243
+ 'push_enabled' => false,
244
+ ], $transactionId);
245
+ }
246
+
247
+ /**
248
+ * CI4 IncomingRequest에서 암호화 패킷을 읽어 JSON 배열로 복호화합니다.
249
+ *
250
+ * @param object $request CodeIgniter\HTTP\IncomingRequest
251
+ * @param bool $requireEncrypted true면 평문 JSON 요청을 거부합니다.
252
+ */
253
+ public function readRequestBody(object $request, ?bool $requireEncrypted = null): array
254
+ {
255
+ $requireEncrypted = $requireEncrypted ?? $this->requireEncryptedRequest;
256
+
257
+ $contentType = '';
258
+ if (method_exists($request, 'getHeaderLine')) {
259
+ $contentType = strtolower((string) $request->getHeaderLine('Content-Type'));
260
+ }
261
+
262
+ $rawBody = '';
263
+ if (method_exists($request, 'getBody')) {
264
+ $rawBody = (string) $request->getBody();
265
+ }
266
+
267
+ $isEncryptedPacket = str_contains($contentType, 'application/octet-stream');
268
+
269
+ if ($requireEncrypted && !$isEncryptedPacket) {
270
+ throw new \RuntimeException('Encrypted request required: Content-Type must be application/octet-stream');
271
+ }
272
+
273
+ if ($isEncryptedPacket) {
274
+ if ($rawBody === '') {
275
+ throw new \RuntimeException('Encrypted request body is empty');
276
+ }
277
+
278
+ $jsonStr = $this->decryptPacket($rawBody);
279
+ $decoded = json_decode($jsonStr, true);
280
+ if (!is_array($decoded)) {
281
+ throw new \RuntimeException('Invalid encrypted JSON payload');
282
+ }
283
+ return $decoded;
284
+ }
285
+
286
+ // 선택적으로 평문 허용할 때만 fallback
287
+ if ($rawBody === '') {
288
+ return [];
289
+ }
290
+ $decoded = json_decode($rawBody, true);
291
+ if (!is_array($decoded)) {
292
+ throw new \RuntimeException('Invalid JSON payload');
293
+ }
294
+ return $decoded;
295
+ }
296
+
158
297
  // ─── 내부 ─────────────────────────────────────────────────────────────────
159
298
 
160
299
  private function request(string $method, string $path, array $body = [], array $extraHeaders = []): array
@@ -4,6 +4,7 @@ namespace App\Services;
4
4
 
5
5
  use Illuminate\Support\Facades\Http;
6
6
  use Illuminate\Support\Str;
7
+ use Illuminate\Http\Request;
7
8
 
8
9
  /**
9
10
  * Entity Server 클라이언트 서비스 (Laravel)
@@ -40,6 +41,7 @@ class EntityServerService
40
41
  private string $apiKey;
41
42
  private string $hmacSecret;
42
43
  private int $magicLen;
44
+ private bool $requireEncryptedRequest;
43
45
  private ?string $activeTxId = null;
44
46
 
45
47
  public function __construct()
@@ -48,6 +50,7 @@ class EntityServerService
48
50
  $this->apiKey = config('services.entity_server.api_key', env('ENTITY_SERVER_API_KEY', ''));
49
51
  $this->hmacSecret = config('services.entity_server.hmac_secret', env('ENTITY_SERVER_HMAC_SECRET', ''));
50
52
  $this->magicLen = (int) config('services.entity_server.packet_magic_len', env('ENTITY_PACKET_MAGIC_LEN', 4));
53
+ $this->requireEncryptedRequest = (bool) config('services.entity_server.require_encrypted_request', env('ENTITY_REQUIRE_ENCRYPTED_REQUEST', true));
51
54
  }
52
55
 
53
56
  // ─── CRUD ────────────────────────────────────────────────────────────────
@@ -138,6 +141,59 @@ class EntityServerService
138
141
  return $this->request('POST', "/v1/entity/{$entity}/rollback/{$historySeq}");
139
142
  }
140
143
 
144
+ /**
145
+ * 푸시 발송 트리거 엔티티에 submit합니다.
146
+ */
147
+ public function push(string $pushEntity, array $payload, ?string $transactionId = null): array
148
+ {
149
+ return $this->submit($pushEntity, $payload, $transactionId);
150
+ }
151
+
152
+ /**
153
+ * push_log 목록 조회 헬퍼
154
+ */
155
+ public function pushLogList(array $params = []): array
156
+ {
157
+ return $this->list('push_log', $params);
158
+ }
159
+
160
+ /**
161
+ * Laravel Request에서 암호화 패킷 또는 평문 JSON 본문을 읽어 배열로 반환합니다.
162
+ */
163
+ public function readRequestBody(Request $request, ?bool $requireEncrypted = null): array
164
+ {
165
+ $requireEncrypted = $requireEncrypted ?? $this->requireEncryptedRequest;
166
+
167
+ $contentType = strtolower((string) $request->header('Content-Type', ''));
168
+ $rawBody = (string) $request->getContent();
169
+ $isEncrypted = str_contains($contentType, 'application/octet-stream');
170
+
171
+ if ($requireEncrypted && !$isEncrypted) {
172
+ throw new \RuntimeException('Encrypted request required: Content-Type must be application/octet-stream');
173
+ }
174
+
175
+ if ($isEncrypted) {
176
+ if ($rawBody === '') {
177
+ throw new \RuntimeException('Encrypted request body is empty');
178
+ }
179
+ $jsonStr = $this->decryptPacket($rawBody);
180
+ $decoded = json_decode($jsonStr, true);
181
+ if (!is_array($decoded)) {
182
+ throw new \RuntimeException('Invalid encrypted JSON payload');
183
+ }
184
+ return $decoded;
185
+ }
186
+
187
+ if ($rawBody === '') {
188
+ return [];
189
+ }
190
+ $decoded = json_decode($rawBody, true);
191
+ if (!is_array($decoded)) {
192
+ throw new \RuntimeException('Invalid JSON payload');
193
+ }
194
+ return $decoded;
195
+ }
196
+
141
197
  // ─── 내부 ─────────────────────────────────────────────────────────────────
142
198
 
143
199
  private function request(string $method, string $path, array $body = [], array $extraHeaders = []): array
@@ -150,6 +150,112 @@ class EntityServerClient:
150
150
  """history seq 단위 롤백 (단건)"""
151
151
  return self._request("POST", f"/v1/entity/{entity}/rollback/{history_seq}")
152
152
 
153
+ def push(self, push_entity: str, payload: dict, *, transaction_id: str | None = None) -> dict:
154
+ """푸시 발송 트리거 엔티티에 submit합니다."""
155
+ return self.submit(push_entity, payload, transaction_id=transaction_id)
156
+
157
+ def push_log_list(self, page: int = 1, limit: int = 20, order_by: str | None = None) -> dict:
158
+ """push_log 목록 조회 헬퍼"""
159
+ return self.list("push_log", page=page, limit=limit, order_by=order_by)
160
+
161
+ def register_push_device(
162
+ self,
163
+ account_seq: int,
164
+ device_id: str,
165
+ push_token: str,
166
+ *,
167
+ platform: str | None = None,
168
+ device_type: str | None = None,
169
+ browser: str | None = None,
170
+ browser_version: str | None = None,
171
+ push_enabled: bool = True,
172
+ transaction_id: str | None = None,
173
+ ) -> dict:
174
+ """account_device 디바이스 등록/갱신 헬퍼 (push_token 단일 필드)"""
175
+ payload: dict[str, Any] = {
176
+ "id": device_id,
177
+ "account_seq": account_seq,
178
+ "push_token": push_token,
179
+ "push_enabled": push_enabled,
180
+ }
181
+ if platform:
182
+ payload["platform"] = platform
183
+ if device_type:
184
+ payload["device_type"] = device_type
185
+ if browser:
186
+ payload["browser"] = browser
187
+ if browser_version:
188
+ payload["browser_version"] = browser_version
189
+ return self.submit("account_device", payload, transaction_id=transaction_id)
190
+
191
+ def update_push_device_token(
192
+ self,
193
+ device_seq: int,
194
+ push_token: str,
195
+ *,
196
+ push_enabled: bool = True,
197
+ transaction_id: str | None = None,
198
+ ) -> dict:
199
+ """account_device.seq 기준 push_token 갱신 헬퍼"""
200
+ return self.submit(
201
+ "account_device",
202
+ {
203
+ "seq": device_seq,
204
+ "push_token": push_token,
205
+ "push_enabled": push_enabled,
206
+ },
207
+ transaction_id=transaction_id,
208
+ )
209
+
210
+ def disable_push_device(
211
+ self,
212
+ device_seq: int,
213
+ *,
214
+ transaction_id: str | None = None,
215
+ ) -> dict:
216
+ """account_device.seq 기준 푸시 수신 비활성화 헬퍼"""
217
+ return self.submit(
218
+ "account_device",
219
+ {
220
+ "seq": device_seq,
221
+ "push_enabled": False,
222
+ },
223
+ transaction_id=transaction_id,
224
+ )
225
+
226
+ def read_request_body(
227
+ self,
228
+ raw_body: bytes | str | None,
229
+ content_type: str = "application/json",
230
+ *,
231
+ require_encrypted: bool = False,
232
+ ) -> dict:
233
+ """요청 본문을 읽어 JSON으로 반환합니다.
234
+ - application/octet-stream: 암호 패킷 복호화
235
+ - 그 외: 평문 JSON 파싱
236
+ """
237
+ lowered = (content_type or "").lower()
238
+ is_encrypted = "application/octet-stream" in lowered
239
+
240
+ if require_encrypted and not is_encrypted:
241
+ raise RuntimeError(
242
+ "Encrypted request required: Content-Type must be application/octet-stream"
243
+ )
244
+
245
+ if is_encrypted:
246
+ if raw_body in (None, b"", ""):
247
+ raise RuntimeError("Encrypted request body is empty")
248
+
249
+ packet = raw_body if isinstance(raw_body, bytes) else raw_body.encode("utf-8")
250
+ return json.loads(self._decrypt_packet(packet))
251
+
252
+ if raw_body in (None, b"", ""):
253
+ return {}
254
+
255
+ if isinstance(raw_body, bytes):
256
+ return json.loads(raw_body.decode("utf-8"))
257
+ return json.loads(raw_body)
258
+
153
259
  # ─── 내부 ─────────────────────────────────────────────────────────────────
154
260
 
155
261
  def _request(
@@ -39,6 +39,15 @@ export interface EntityQueryFilter {
39
39
  value: unknown;
40
40
  }
41
41
 
42
+ export interface RegisterPushDeviceOptions {
43
+ platform?: string;
44
+ deviceType?: string;
45
+ browser?: string;
46
+ browserVersion?: string;
47
+ pushEnabled?: boolean;
48
+ transactionId?: string;
49
+ }
50
+
42
51
  export class EntityServerClient {
43
52
  private baseUrl: string;
44
53
  private token: string;
@@ -220,6 +229,120 @@ export class EntityServerClient {
220
229
  );
221
230
  }
222
231
 
232
+ push<T = unknown>(
233
+ pushEntity: string,
234
+ payload: Record<string, unknown>,
235
+ opts: { transactionId?: string } = {},
236
+ ): Promise<{ ok: boolean; seq?: number; data?: T }> {
237
+ return this.submit<T>(pushEntity, payload, opts);
238
+ }
239
+
240
+ pushLogList<T = unknown>(
241
+ params: EntityListParams = {},
242
+ ): Promise<{ ok: boolean; data: T[]; total: number }> {
243
+ return this.list<T>("push_log", params);
244
+ }
245
+
246
+ registerPushDevice<T = unknown>(
247
+ accountSeq: number,
248
+ deviceId: string,
249
+ pushToken: string,
250
+ opts: RegisterPushDeviceOptions = {},
251
+ ): Promise<{ ok: boolean; seq?: number; data?: T }> {
252
+ const {
253
+ platform,
254
+ deviceType,
255
+ browser,
256
+ browserVersion,
257
+ pushEnabled = true,
258
+ transactionId,
259
+ } = opts;
260
+
261
+ return this.submit<T>(
262
+ "account_device",
263
+ {
264
+ id: deviceId,
265
+ account_seq: accountSeq,
266
+ push_token: pushToken,
267
+ push_enabled: pushEnabled,
268
+ ...(platform ? { platform } : {}),
269
+ ...(deviceType ? { device_type: deviceType } : {}),
270
+ ...(browser ? { browser } : {}),
271
+ ...(browserVersion ? { browser_version: browserVersion } : {}),
272
+ },
273
+ { transactionId },
274
+ );
275
+ }
276
+
277
+ updatePushDeviceToken<T = unknown>(
278
+ deviceSeq: number,
279
+ pushToken: string,
280
+ opts: { pushEnabled?: boolean; transactionId?: string } = {},
281
+ ): Promise<{ ok: boolean; seq?: number; data?: T }> {
282
+ const { pushEnabled = true, transactionId } = opts;
283
+ return this.submit<T>(
284
+ "account_device",
285
+ {
286
+ seq: deviceSeq,
287
+ push_token: pushToken,
288
+ push_enabled: pushEnabled,
289
+ },
290
+ { transactionId },
291
+ );
292
+ }
293
+
294
+ disablePushDevice<T = unknown>(
295
+ deviceSeq: number,
296
+ opts: { transactionId?: string } = {},
297
+ ): Promise<{ ok: boolean; seq?: number; data?: T }> {
298
+ return this.submit<T>(
299
+ "account_device",
300
+ {
301
+ seq: deviceSeq,
302
+ push_enabled: false,
303
+ },
304
+ { transactionId: opts.transactionId },
305
+ );
306
+ }
307
+
308
+ readRequestBody<T = Record<string, unknown>>(
309
+ body: ArrayBuffer | Uint8Array | string | T | null | undefined,
310
+ contentType = "application/json",
311
+ requireEncrypted = false,
312
+ ): T {
313
+ const lowered = contentType.toLowerCase();
314
+ const isEncrypted = lowered.includes("application/octet-stream");
315
+
316
+ if (requireEncrypted && !isEncrypted) {
317
+ throw new Error(
318
+ "Encrypted request required: Content-Type must be application/octet-stream",
319
+ );
320
+ }
321
+
322
+ if (isEncrypted) {
323
+ if (body == null) {
324
+ throw new Error("Encrypted request body is empty");
325
+ }
326
+ if (body instanceof ArrayBuffer) {
327
+ return this.decryptPacket<T>(body);
328
+ }
329
+ if (body instanceof Uint8Array) {
330
+ const sliced = body.buffer.slice(
331
+ body.byteOffset,
332
+ body.byteOffset + body.byteLength,
333
+ );
334
+ return this.decryptPacket<T>(sliced);
335
+ }
336
+ throw new Error(
337
+ "Encrypted request body must be ArrayBuffer or Uint8Array",
338
+ );
339
+ }
340
+
341
+ if (body == null || body === "") return {} as T;
342
+ if (typeof body === "string") return JSON.parse(body) as T;
343
+ return body as T;
344
+ }
345
+
223
346
  private async request<T>(
224
347
  method: string,
225
348
  path: string,
@@ -103,3 +103,71 @@ export function useEntityRollback(entity: string) {
103
103
  },
104
104
  });
105
105
  }
106
+
107
+ /** 푸시 디바이스 등록/갱신 */
108
+ export function useRegisterPushDevice() {
109
+ const qc = useQueryClient();
110
+ return useMutation({
111
+ mutationFn: (args: {
112
+ accountSeq: number;
113
+ deviceId: string;
114
+ pushToken: string;
115
+ platform?: string;
116
+ deviceType?: string;
117
+ browser?: string;
118
+ browserVersion?: string;
119
+ pushEnabled?: boolean;
120
+ transactionId?: string;
121
+ }) =>
122
+ entityServer.registerPushDevice(
123
+ args.accountSeq,
124
+ args.deviceId,
125
+ args.pushToken,
126
+ {
127
+ platform: args.platform,
128
+ deviceType: args.deviceType,
129
+ browser: args.browser,
130
+ browserVersion: args.browserVersion,
131
+ pushEnabled: args.pushEnabled,
132
+ transactionId: args.transactionId,
133
+ },
134
+ ),
135
+ onSuccess: () => {
136
+ qc.invalidateQueries({ queryKey: ["entity", "account_device"] });
137
+ },
138
+ });
139
+ }
140
+
141
+ /** 푸시 디바이스 토큰 갱신 */
142
+ export function useUpdatePushDeviceToken() {
143
+ const qc = useQueryClient();
144
+ return useMutation({
145
+ mutationFn: (args: {
146
+ deviceSeq: number;
147
+ pushToken: string;
148
+ pushEnabled?: boolean;
149
+ transactionId?: string;
150
+ }) =>
151
+ entityServer.updatePushDeviceToken(args.deviceSeq, args.pushToken, {
152
+ pushEnabled: args.pushEnabled,
153
+ transactionId: args.transactionId,
154
+ }),
155
+ onSuccess: () => {
156
+ qc.invalidateQueries({ queryKey: ["entity", "account_device"] });
157
+ },
158
+ });
159
+ }
160
+
161
+ /** 푸시 디바이스 비활성화 */
162
+ export function useDisablePushDevice() {
163
+ const qc = useQueryClient();
164
+ return useMutation({
165
+ mutationFn: (args: { deviceSeq: number; transactionId?: string }) =>
166
+ entityServer.disablePushDevice(args.deviceSeq, {
167
+ transactionId: args.transactionId,
168
+ }),
169
+ onSuccess: () => {
170
+ qc.invalidateQueries({ queryKey: ["entity", "account_device"] });
171
+ },
172
+ });
173
+ }