From 37457cd6cd1b9396a99733698deecd33f437fe35 Mon Sep 17 00:00:00 2001
From: DJ Mountney <david@twkie.net>
Date: Tue, 7 May 2024 07:38:02 -0700
Subject: [PATCH] Create types for the external versions of entities meant for
 the API (#2716)

* Create types for the external versions of entities meant for the API

- Otherwise the types used in the API were actually wrong vs the data we were sending

* Add release note
---
 packages/loot-core/src/server/api-models.ts   | 70 ++++++++++++-------
 .../loot-core/src/types/api-handlers.d.ts     | 22 +++---
 upcoming-release-notes/2716.md                |  6 ++
 3 files changed, 60 insertions(+), 38 deletions(-)
 create mode 100644 upcoming-release-notes/2716.md

diff --git a/packages/loot-core/src/server/api-models.ts b/packages/loot-core/src/server/api-models.ts
index f3d146067..69be7a40a 100644
--- a/packages/loot-core/src/server/api-models.ts
+++ b/packages/loot-core/src/server/api-models.ts
@@ -1,10 +1,21 @@
-// @ts-strict-ignore
+import type {
+  AccountEntity,
+  CategoryEntity,
+  CategoryGroupEntity,
+  PayeeEntity,
+} from '../types/models';
+
 import * as models from './models';
 
+export type APIAccountEntity = Pick<AccountEntity, 'id' | 'name'> & {
+  offbudget: boolean;
+  closed: boolean;
+};
+
 export const accountModel = {
   ...models.accountModel,
 
-  toExternal(account) {
+  toExternal(account: AccountEntity): APIAccountEntity {
     return {
       id: account.id,
       name: account.name,
@@ -13,8 +24,8 @@ export const accountModel = {
     };
   },
 
-  fromExternal(account) {
-    const result = { ...account };
+  fromExternal(account: APIAccountEntity) {
+    const result = { ...account } as unknown as AccountEntity;
     if ('offbudget' in account) {
       result.offbudget = account.offbudget ? 1 : 0;
     }
@@ -25,10 +36,17 @@ export const accountModel = {
   },
 };
 
+export type APICategoryEntity = Pick<
+  CategoryEntity,
+  'id' | 'name' | 'is_income' | 'hidden'
+> & {
+  group_id?: string;
+};
+
 export const categoryModel = {
   ...models.categoryModel,
 
-  toExternal(category) {
+  toExternal(category: CategoryEntity): APICategoryEntity {
     return {
       id: category.id,
       name: category.name,
@@ -38,14 +56,10 @@ export const categoryModel = {
     };
   },
 
-  fromExternal(category) {
-    const { group_id: _, ...result } = category;
-    if ('is_income' in category) {
-      result.is_income = category.is_income ? 1 : 0;
-    }
-    if ('hidden' in category) {
-      result.hidden = category.hidden ? 1 : 0;
-    }
+  fromExternal(category: APICategoryEntity) {
+    const { group_id: _, ...result }: { group_id?: string } & CategoryEntity =
+      category;
+
     if ('group_id' in category) {
       result.cat_group = category.group_id;
     }
@@ -53,27 +67,28 @@ export const categoryModel = {
   },
 };
 
+export type APICategoryGroupEntity = Pick<
+  CategoryGroupEntity,
+  'id' | 'name' | 'is_income' | 'hidden'
+> & {
+  categories: APICategoryEntity[];
+};
+
 export const categoryGroupModel = {
   ...models.categoryGroupModel,
 
-  toExternal(group) {
+  toExternal(group: CategoryGroupEntity): APICategoryGroupEntity {
     return {
       id: group.id,
       name: group.name,
       is_income: group.is_income ? true : false,
       hidden: group.hidden ? true : false,
-      categories: group.categories.map(categoryModel.toExternal),
+      categories: group.categories?.map(categoryModel.toExternal) || [],
     };
   },
 
-  fromExternal(group) {
-    const result = { ...group };
-    if ('is_income' in group) {
-      result.is_income = group.is_income ? 1 : 0;
-    }
-    if ('hidden' in group) {
-      result.hidden = group.hidden ? 1 : 0;
-    }
+  fromExternal(group: APICategoryGroupEntity) {
+    const result = { ...group } as unknown as CategoryGroupEntity;
     if ('categories' in group) {
       result.categories = group.categories.map(categoryModel.fromExternal);
     }
@@ -81,20 +96,21 @@ export const categoryGroupModel = {
   },
 };
 
+export type APIPayeeEntity = Pick<PayeeEntity, 'id' | 'name' | 'transfer_acct'>;
+
 export const payeeModel = {
   ...models.payeeModel,
 
-  toExternal(payee) {
+  toExternal(payee: PayeeEntity) {
     return {
       id: payee.id,
       name: payee.name,
-      category: payee.category,
       transfer_acct: payee.transfer_acct,
     };
   },
 
-  fromExternal(payee) {
+  fromExternal(payee: APIPayeeEntity) {
     // No translation is needed
-    return payee;
+    return payee as PayeeEntity;
   },
 };
diff --git a/packages/loot-core/src/types/api-handlers.d.ts b/packages/loot-core/src/types/api-handlers.d.ts
index c1f42776f..5fc820e4a 100644
--- a/packages/loot-core/src/types/api-handlers.d.ts
+++ b/packages/loot-core/src/types/api-handlers.d.ts
@@ -1,12 +1,12 @@
 import { type batchUpdateTransactions } from '../server/accounts/transactions';
-
 import type {
-  TransactionEntity,
-  AccountEntity,
-  CategoryGroupEntity,
-  CategoryEntity,
-  PayeeEntity,
-} from './models';
+  APICategoryEntity,
+  APIAccountEntity,
+  APICategoryGroupEntity,
+  APIPayeeEntity,
+} from '../server/api-models';
+
+import type { TransactionEntity } from './models';
 import { type ServerHandlers } from './server-handlers';
 
 export interface ApiHandlers {
@@ -96,7 +96,7 @@ export interface ApiHandlers {
 
   'api/sync': () => Promise<void>;
 
-  'api/accounts-get': () => Promise<AccountEntity[]>;
+  'api/accounts-get': () => Promise<APIAccountEntity[]>;
 
   'api/account-create': (arg: { account; initialBalance? }) => Promise<string>;
 
@@ -114,9 +114,9 @@ export interface ApiHandlers {
 
   'api/categories-get': (arg: {
     grouped;
-  }) => Promise<Array<CategoryGroupEntity | CategoryEntity>>;
+  }) => Promise<Array<APICategoryGroupEntity | APICategoryEntity>>;
 
-  'api/category-groups-get': () => Promise<CategoryGroupEntity[]>;
+  'api/category-groups-get': () => Promise<APICategoryGroupEntity[]>;
 
   'api/category-group-create': (arg: { group }) => Promise<string>;
 
@@ -136,7 +136,7 @@ export interface ApiHandlers {
     transferCategoryId?;
   }) => Promise<{ error?: string }>;
 
-  'api/payees-get': () => Promise<PayeeEntity[]>;
+  'api/payees-get': () => Promise<APIPayeeEntity[]>;
 
   'api/payee-create': (arg: { payee }) => Promise<string>;
 
diff --git a/upcoming-release-notes/2716.md b/upcoming-release-notes/2716.md
new file mode 100644
index 000000000..ab223026e
--- /dev/null
+++ b/upcoming-release-notes/2716.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [twk3]
+---
+
+Create types for the external versions of entities meant for the API`
-- 
GitLab