From 602b84342b60a9ce1ce0d7443b81f5f6f5a18619 Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez Date: Tue, 18 Mar 2025 06:43:15 -0700 Subject: [PATCH] Update server handlers to use the implementation function type instead of duplicating the function arguments/return types (#4650) * Update server handlers types to be more strict * Release notes --- .../admin/UserAccess/UserAccessRow.tsx | 4 +- .../modals/MergeUnusedPayeesModal.tsx | 4 + .../components/schedules/ScheduleDetails.tsx | 4 + packages/loot-core/src/server/admin/app.ts | 132 +++++++++---- .../src/server/admin/types/handlers.ts | 46 ----- .../loot-core/src/server/dashboard/app.ts | 11 +- .../src/server/dashboard/types/handlers.d.ts | 24 --- packages/loot-core/src/server/filters/app.ts | 13 +- .../src/server/filters/types/handlers.d.ts | 7 - packages/loot-core/src/server/notes/app.ts | 7 +- .../src/server/notes/types/handlers.d.ts | 3 - packages/loot-core/src/server/reports/app.ts | 10 +- .../src/server/reports/types/handlers.ts | 9 - packages/loot-core/src/server/rules/app.ts | 177 +++++++++++------- .../src/server/rules/types/handlers.ts | 53 ------ .../loot-core/src/server/schedules/app.ts | 16 +- .../src/server/schedules/find-schedules.ts | 5 +- .../src/server/schedules/types/handlers.ts | 38 ---- packages/loot-core/src/server/tools/app.ts | 18 +- .../src/server/tools/types/handlers.d.ts | 12 -- packages/loot-core/src/types/handlers.d.ts | 16 +- .../loot-core/src/types/models/index.d.ts | 2 +- .../{userAccess.ts => user-access.d.ts} | 0 .../src/types/models/{user.ts => user.d.ts} | 0 upcoming-release-notes/4650.md | 6 + 25 files changed, 282 insertions(+), 335 deletions(-) delete mode 100644 packages/loot-core/src/server/admin/types/handlers.ts delete mode 100644 packages/loot-core/src/server/dashboard/types/handlers.d.ts delete mode 100644 packages/loot-core/src/server/filters/types/handlers.d.ts delete mode 100644 packages/loot-core/src/server/notes/types/handlers.d.ts delete mode 100644 packages/loot-core/src/server/reports/types/handlers.ts delete mode 100644 packages/loot-core/src/server/rules/types/handlers.ts delete mode 100644 packages/loot-core/src/server/schedules/types/handlers.ts delete mode 100644 packages/loot-core/src/server/tools/types/handlers.d.ts rename packages/loot-core/src/types/models/{userAccess.ts => user-access.d.ts} (100%) rename packages/loot-core/src/types/models/{user.ts => user.d.ts} (100%) create mode 100644 upcoming-release-notes/4650.md diff --git a/packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx b/packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx index df5d5bb639..961666ba7f 100644 --- a/packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx +++ b/packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx @@ -45,12 +45,12 @@ export const UserAccessRow = memo( handleError(error); } } else { - const { someDeletionsFailed } = await send('access-delete-all', { + const result = await send('access-delete-all', { fileId: cloudFileId as string, ids: [access.userId], }); - if (someDeletionsFailed) { + if ('someDeletionsFailed' in result && result.someDeletionsFailed) { dispatch( addNotification({ notification: { diff --git a/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx b/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx index 439856b51a..d5e5aebb63 100644 --- a/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx +++ b/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx @@ -85,6 +85,10 @@ export function MergeUnusedPayeesModal({ if (ruleId) { const rule = await send('rule-get', { id: ruleId }); + if (!rule) { + return; + } + dispatch( replaceModal({ modal: { name: 'edit-rule', options: { rule } } }), ); diff --git a/packages/desktop-client/src/components/schedules/ScheduleDetails.tsx b/packages/desktop-client/src/components/schedules/ScheduleDetails.tsx index d5c4505cc3..76e35849e4 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleDetails.tsx +++ b/packages/desktop-client/src/components/schedules/ScheduleDetails.tsx @@ -534,6 +534,10 @@ export function ScheduleDetails({ id, transaction }: ScheduleDetailsProps) { async function onEditRule(id: string) { const rule = await send('rule-get', { id }); + if (!rule) { + return; + } + globalDispatch( pushModal({ modal: { diff --git a/packages/loot-core/src/server/admin/app.ts b/packages/loot-core/src/server/admin/app.ts index 121236c6a4..6dc51dde97 100644 --- a/packages/loot-core/src/server/admin/app.ts +++ b/packages/loot-core/src/server/admin/app.ts @@ -1,16 +1,67 @@ // @ts-strict-ignore import * as asyncStorage from '../../platform/server/asyncStorage'; -import { UserAvailable, UserEntity } from '../../types/models/user'; +import { + UserAvailable, + UserEntity, + NewUserAccessEntity, +} from '../../types/models'; import { createApp } from '../app'; import { del, get, patch, post } from '../post'; import { getServer } from '../server-config'; -import { AdminHandlers } from './types/handlers'; +export type AdminHandlers = { + 'users-get': typeof getUsers; + 'user-delete-all': typeof deleteAllUsers; + 'user-add': typeof addUser; + 'user-update': typeof updateUser; + 'access-add': typeof addAccess; + 'access-delete-all': typeof deleteAllAccess; + 'access-get-available-users': typeof accessGetAvailableUsers; + 'transfer-ownership': typeof transferOwnership; + 'owner-created': typeof ownerCreated; +}; // Expose functions to the client export const app = createApp(); -app.method('user-delete-all', async function (ids) { +app.method('users-get', getUsers); +app.method('user-delete-all', deleteAllUsers); +app.method('user-add', addUser); +app.method('user-update', updateUser); +app.method('access-add', addAccess); +app.method('access-delete-all', deleteAllAccess); +app.method('access-get-available-users', accessGetAvailableUsers); +app.method('transfer-ownership', transferOwnership); +app.method('owner-created', ownerCreated); + +async function getUsers() { + const userToken = await asyncStorage.getItem('user-token'); + + if (userToken) { + const res = await get(getServer().BASE_SERVER + '/admin/users/', { + headers: { + 'X-ACTUAL-TOKEN': userToken, + }, + }); + + if (res) { + try { + const list = JSON.parse(res) as UserEntity[]; + return list; + } catch (err) { + return { error: 'Failed to parse response: ' + err.message }; + } + } + } + + return null; +} + +async function deleteAllUsers( + ids: Array, +): Promise< + { someDeletionsFailed: boolean; ids?: number[] } | { error: string } +> { const userToken = await asyncStorage.getItem('user-token'); if (userToken) { try { @@ -33,32 +84,11 @@ app.method('user-delete-all', async function (ids) { } return { someDeletionsFailed: true }; -}); +} -app.method('users-get', async function () { - const userToken = await asyncStorage.getItem('user-token'); - - if (userToken) { - const res = await get(getServer().BASE_SERVER + '/admin/users/', { - headers: { - 'X-ACTUAL-TOKEN': userToken, - }, - }); - - if (res) { - try { - const list = JSON.parse(res) as UserEntity[]; - return list; - } catch (err) { - return { error: 'Failed to parse response: ' + err.message }; - } - } - } - - return null; -}); - -app.method('user-add', async function (user) { +async function addUser( + user: Omit, +): Promise<{ error: string } | { id: string }> { const userToken = await asyncStorage.getItem('user-token'); if (userToken) { @@ -74,9 +104,11 @@ app.method('user-add', async function (user) { } return null; -}); +} -app.method('user-update', async function (user) { +async function updateUser( + user: Omit, +): Promise<{ error: string } | { id: string }> { const userToken = await asyncStorage.getItem('user-token'); if (userToken) { @@ -92,9 +124,11 @@ app.method('user-update', async function (user) { } return null; -}); +} -app.method('access-add', async function (access) { +async function addAccess( + access: NewUserAccessEntity, +): Promise<{ error?: string } | Record> { const userToken = await asyncStorage.getItem('user-token'); if (userToken) { @@ -110,9 +144,17 @@ app.method('access-add', async function (access) { } return null; -}); +} -app.method('access-delete-all', async function ({ fileId, ids }) { +async function deleteAllAccess({ + fileId, + ids, +}: { + fileId: string; + ids: string[]; +}): Promise< + { someDeletionsFailed: boolean; ids?: number[] } | { error: unknown } +> { const userToken = await asyncStorage.getItem('user-token'); if (userToken) { try { @@ -133,9 +175,11 @@ app.method('access-delete-all', async function ({ fileId, ids }) { } return { someDeletionsFailed: true }; -}); +} -app.method('access-get-available-users', async function (fileId) { +async function accessGetAvailableUsers( + fileId: string, +): Promise { const userToken = await asyncStorage.getItem('user-token'); if (userToken) { @@ -158,9 +202,15 @@ app.method('access-get-available-users', async function (fileId) { } return []; -}); +} -app.method('transfer-ownership', async function ({ fileId, newUserId }) { +async function transferOwnership({ + fileId, + newUserId, +}: { + fileId: string; + newUserId: string; +}): Promise<{ error?: string } | Record> { const userToken = await asyncStorage.getItem('user-token'); if (userToken) { @@ -178,9 +228,9 @@ app.method('transfer-ownership', async function ({ fileId, newUserId }) { } return {}; -}); +} -app.method('owner-created', async function () { +async function ownerCreated() { const res = await get(getServer().BASE_SERVER + '/admin/owner-created/'); if (res) { @@ -188,4 +238,4 @@ app.method('owner-created', async function () { } return null; -}); +} diff --git a/packages/loot-core/src/server/admin/types/handlers.ts b/packages/loot-core/src/server/admin/types/handlers.ts deleted file mode 100644 index 359471820b..0000000000 --- a/packages/loot-core/src/server/admin/types/handlers.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { UserAvailable, UserEntity } from '../../../types/models/user'; -import { NewUserAccessEntity } from '../../../types/models/userAccess'; - -export interface AdminHandlers { - 'users-get': () => Promise; - - 'user-delete-all': ( - ids: string[], - ) => Promise< - { someDeletionsFailed: boolean; ids?: number[] } | { error: string } - >; - - 'user-add': ( - user: Omit, - ) => Promise<{ error: string } | { id: string }>; - - 'user-update': ( - user: Omit, - ) => Promise<{ error: string } | { id: string }>; - - 'access-add': ( - user: NewUserAccessEntity, - ) => Promise<{ error?: string } | Record>; - - 'access-delete-all': ({ - fileId, - ids, - }: { - fileId: string; - ids: string[]; - }) => Promise<{ someDeletionsFailed: boolean; ids?: number[] }>; - - 'access-get-available-users': ( - fileId: string, - ) => Promise; - - 'transfer-ownership': ({ - fileId, - newUserId, - }: { - fileId: string; - newUserId: string; - }) => Promise<{ error?: string } | Record>; - - 'owner-created': () => Promise; -} diff --git a/packages/loot-core/src/server/dashboard/app.ts b/packages/loot-core/src/server/dashboard/app.ts index 096977d5c9..da9564f2da 100644 --- a/packages/loot-core/src/server/dashboard/app.ts +++ b/packages/loot-core/src/server/dashboard/app.ts @@ -21,8 +21,6 @@ import { reportModel } from '../reports/app'; import { batchMessages } from '../sync'; import { undoable } from '../undo'; -import { DashboardHandlers } from './types/handlers'; - function isExportedCustomReportWidget( widget: ExportImportDashboardWidget, ): widget is ExportImportCustomReportWidget { @@ -245,6 +243,15 @@ async function importDashboard({ filepath }: { filepath: string }) { } } +export type DashboardHandlers = { + 'dashboard-update': typeof updateDashboard; + 'dashboard-update-widget': typeof updateDashboardWidget; + 'dashboard-reset': typeof resetDashboard; + 'dashboard-add-widget': typeof addDashboardWidget; + 'dashboard-remove-widget': typeof removeDashboardWidget; + 'dashboard-import': typeof importDashboard; +}; + export const app = createApp(); app.method('dashboard-update', mutator(undoable(updateDashboard))); diff --git a/packages/loot-core/src/server/dashboard/types/handlers.d.ts b/packages/loot-core/src/server/dashboard/types/handlers.d.ts deleted file mode 100644 index 8d6e8319d3..0000000000 --- a/packages/loot-core/src/server/dashboard/types/handlers.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { type Widget } from '../../../types/models'; -import { type EverythingButIdOptional } from '../../../types/util'; - -export interface DashboardHandlers { - 'dashboard-update': ( - widgets: EverythingButIdOptional>[], - ) => Promise; - 'dashboard-update-widget': ( - widget: EverythingButIdOptional>, - ) => Promise; - 'dashboard-reset': () => Promise; - 'dashboard-add-widget': ( - widget: Omit & - Partial>, - ) => Promise; - 'dashboard-remove-widget': (widgetId: string) => Promise; - 'dashboard-import': (args: { - filepath: string; - }) => Promise< - | { status: 'ok' } - | { error: 'json-parse-error' | 'internal-error' } - | { error: 'validation-error'; message: string } - >; -} diff --git a/packages/loot-core/src/server/filters/app.ts b/packages/loot-core/src/server/filters/app.ts index ac4059285b..ecd32ce0f0 100644 --- a/packages/loot-core/src/server/filters/app.ts +++ b/packages/loot-core/src/server/filters/app.ts @@ -1,6 +1,7 @@ // @ts-strict-ignore import { v4 as uuidv4 } from 'uuid'; +import { TransactionFilterEntity } from '../../types/models'; import { createApp } from '../app'; import * as db from '../db'; import { requiredFields } from '../models'; @@ -8,8 +9,6 @@ import { mutator } from '../mutators'; import { parseConditionsOrActions } from '../transactions/transaction-rules'; import { undoable } from '../undo'; -import { FiltersHandlers } from './types/handlers'; - const filterModel = { validate(filter, { update }: { update?: boolean } = {}) { requiredFields('transaction_filters', filter, ['conditions'], update); @@ -110,7 +109,7 @@ function filterOptionsMatch(options1, options2) { return keys1.every(key => opt1[key] === opt2[key]); } -async function createFilter(filter) { +async function createFilter(filter): Promise { const filterId = uuidv4(); const item = { id: filterId, @@ -175,10 +174,16 @@ async function updateFilter(filter) { await db.updateWithSchema('transaction_filters', filterModel.fromJS(item)); } -async function deleteFilter(id) { +async function deleteFilter(id: TransactionFilterEntity['id']) { await db.delete_('transaction_filters', id); } +export type FiltersHandlers = { + 'filter-create': typeof createFilter; + 'filter-update': typeof updateFilter; + 'filter-delete': typeof deleteFilter; +}; + export const app = createApp(); app.method('filter-create', mutator(createFilter)); diff --git a/packages/loot-core/src/server/filters/types/handlers.d.ts b/packages/loot-core/src/server/filters/types/handlers.d.ts deleted file mode 100644 index 2606be1a79..0000000000 --- a/packages/loot-core/src/server/filters/types/handlers.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface FiltersHandlers { - 'filter-create': (filter: object) => Promise; - - 'filter-update': (filter: object) => Promise; - - 'filter-delete': (id: string) => Promise; -} diff --git a/packages/loot-core/src/server/notes/app.ts b/packages/loot-core/src/server/notes/app.ts index aac00a662e..a597cd9b70 100644 --- a/packages/loot-core/src/server/notes/app.ts +++ b/packages/loot-core/src/server/notes/app.ts @@ -2,12 +2,13 @@ import { NoteEntity } from '../../types/models'; import { createApp } from '../app'; import * as db from '../db'; -import { NotesHandlers } from './types/handlers'; +export type NotesHandlers = { + 'notes-save': typeof updateNotes; +}; export const app = createApp(); +app.method('notes-save', updateNotes); async function updateNotes({ id, note }: NoteEntity) { await db.update('notes', { id, note }); } - -app.method('notes-save', updateNotes); diff --git a/packages/loot-core/src/server/notes/types/handlers.d.ts b/packages/loot-core/src/server/notes/types/handlers.d.ts deleted file mode 100644 index 2c4f576a54..0000000000 --- a/packages/loot-core/src/server/notes/types/handlers.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface NotesHandlers { - 'notes-save': (arg: { id; note }) => Promise; -} diff --git a/packages/loot-core/src/server/reports/app.ts b/packages/loot-core/src/server/reports/app.ts index f5d3beeb71..a252e25d37 100644 --- a/packages/loot-core/src/server/reports/app.ts +++ b/packages/loot-core/src/server/reports/app.ts @@ -11,8 +11,6 @@ import { requiredFields } from '../models'; import { mutator } from '../mutators'; import { undoable } from '../undo'; -import { ReportsHandlers } from './types/handlers'; - export const reportModel = { validate( report: Omit, @@ -148,10 +146,16 @@ async function updateReport(item: CustomReportEntity) { await db.updateWithSchema('custom_reports', reportModel.fromJS(item)); } -async function deleteReport(id: string) { +async function deleteReport(id: CustomReportEntity['id']) { await db.delete_('custom_reports', id); } +export type ReportsHandlers = { + 'report/create': typeof createReport; + 'report/update': typeof updateReport; + 'report/delete': typeof deleteReport; +}; + // Expose functions to the client export const app = createApp(); diff --git a/packages/loot-core/src/server/reports/types/handlers.ts b/packages/loot-core/src/server/reports/types/handlers.ts deleted file mode 100644 index 4c8d3fbc6a..0000000000 --- a/packages/loot-core/src/server/reports/types/handlers.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type CustomReportEntity } from '../../../types/models'; - -export interface ReportsHandlers { - 'report/create': (report: CustomReportEntity) => Promise; - - 'report/update': (report: CustomReportEntity) => Promise; - - 'report/delete': (id: string) => Promise; -} diff --git a/packages/loot-core/src/server/rules/app.ts b/packages/loot-core/src/server/rules/app.ts index a13de653fc..37cce38d1d 100644 --- a/packages/loot-core/src/server/rules/app.ts +++ b/packages/loot-core/src/server/rules/app.ts @@ -1,5 +1,9 @@ // @ts-strict-ignore -import { type RuleEntity } from '../../types/models'; +import { + RuleActionEntity, + TransactionEntity, + type RuleEntity, +} from '../../types/models'; import { createApp } from '../app'; import { RuleError } from '../errors'; import { mutator } from '../mutators'; @@ -7,8 +11,6 @@ import { batchMessages } from '../sync'; import * as rules from '../transactions/transaction-rules'; import { undoable } from '../undo'; -import { RulesHandlers } from './types/handlers'; - import { Condition, Action, rankRules } from '.'; function validateRule(rule: Partial) { @@ -58,90 +60,127 @@ function validateRule(rule: Partial) { return null; } +type ValidationError = { + conditionErrors: string[]; + actionErrors: string[]; +}; + +export type RulesHandlers = { + 'rule-validate': typeof ruleValidate; + 'rule-add': typeof addRule; + 'rule-update': typeof updateRule; + 'rule-delete': typeof deleteRule; + 'rule-delete-all': typeof deleteAllRules; + 'rule-apply-actions': typeof applyRuleActions; + 'rule-add-payee-rename': typeof addRulePayeeRename; + 'rules-get': typeof getRules; + 'rule-get': typeof getRule; + 'rules-run': typeof runRules; +}; + // Expose functions to the client export const app = createApp(); -app.method('rule-validate', async function (rule) { +app.method('rule-validate', ruleValidate); +app.method('rule-add', mutator(addRule)); +app.method('rule-update', mutator(updateRule)); +app.method('rule-delete', mutator(deleteRule)); +app.method('rule-delete-all', mutator(deleteAllRules)); +app.method('rule-apply-actions', mutator(undoable(applyRuleActions))); +app.method('rule-add-payee-rename', mutator(addRulePayeeRename)); +app.method('rules-get', getRules); +app.method('rule-get', getRule); +app.method('rules-run', runRules); + +async function ruleValidate( + rule: Partial, +): Promise<{ error: ValidationError | null }> { const error = validateRule(rule); return { error }; -}); +} -app.method( - 'rule-add', - mutator(async function (rule) { - const error = validateRule(rule); - if (error) { - return { error }; - } +async function addRule( + rule: Omit, +): Promise<{ error: ValidationError } | RuleEntity> { + const error = validateRule(rule); + if (error) { + return { error }; + } - const id = await rules.insertRule(rule); - return { id, ...rule }; - }), -); + const id = await rules.insertRule(rule); + return { id, ...rule }; +} -app.method( - 'rule-update', - mutator(async function (rule) { - const error = validateRule(rule); - if (error) { - return { error }; - } +async function updateRule< + PartialRule extends Partial> & Pick, +>(rule: PartialRule): Promise<{ error: ValidationError } | PartialRule> { + const error = validateRule(rule); + if (error) { + return { error }; + } - await rules.updateRule(rule); - return rule; - }), -); + await rules.updateRule(rule); + return rule; +} -app.method( - 'rule-delete', - mutator(async function (id) { - return rules.deleteRule(id); - }), -); +async function deleteRule(id: RuleEntity['id']) { + return rules.deleteRule(id); +} -app.method( - 'rule-delete-all', - mutator(async function (ids) { - let someDeletionsFailed = false; +async function deleteAllRules( + ids: Array, +): Promise<{ someDeletionsFailed: boolean }> { + let someDeletionsFailed = false; - await batchMessages(async () => { - for (const id of ids) { - const res = await rules.deleteRule(id); - if (res === false) { - someDeletionsFailed = true; - } + await batchMessages(async () => { + for (const id of ids) { + const res = await rules.deleteRule(id); + if (res === false) { + someDeletionsFailed = true; } - }); + } + }); - return { someDeletionsFailed }; - }), -); + return { someDeletionsFailed }; +} -app.method( - 'rule-apply-actions', - mutator( - undoable(async function ({ transactions, actions }) { - return rules.applyActions(transactions, actions); - }), - ), -); +async function applyRuleActions({ + transactions, + actions, +}: { + transactions: TransactionEntity[]; + actions: Array; +}): Promise { + return rules.applyActions(transactions, actions); +} -app.method( - 'rule-add-payee-rename', - mutator(async function ({ fromNames, to }) { - return rules.updatePayeeRenameRule(fromNames, to); - }), -); +async function addRulePayeeRename({ + fromNames, + to, +}: { + fromNames: string[]; + to: string; +}): Promise { + return rules.updatePayeeRenameRule(fromNames, to); +} -app.method('rules-get', async function () { - return rankRules(rules.getRules()).map(rule => rule.serialize()); -}); - -app.method('rule-get', async function ({ id }) { +async function getRule({ + id, +}: { + id: RuleEntity['id']; +}): Promise { const rule = rules.getRules().find(rule => rule.id === id); return rule ? rule.serialize() : null; -}); +} -app.method('rules-run', async function ({ transaction }) { +async function getRules() { + return rankRules(rules.getRules()).map(rule => rule.serialize()); +} + +async function runRules({ + transaction, +}: { + transaction: TransactionEntity; +}): Promise { return rules.runRules(transaction); -}); +} diff --git a/packages/loot-core/src/server/rules/types/handlers.ts b/packages/loot-core/src/server/rules/types/handlers.ts deleted file mode 100644 index 18b76a7ce0..0000000000 --- a/packages/loot-core/src/server/rules/types/handlers.ts +++ /dev/null @@ -1,53 +0,0 @@ -// @ts-strict-ignore -import { type Action } from '..'; -import { - type RuleEntity, - type TransactionEntity, - type RuleActionEntity, -} from '../../../types/models'; - -type ValidationError = { - conditionErrors: string[]; - actionErrors: string[]; -}; - -export interface RulesHandlers { - 'rule-validate': ( - rule: Partial, - ) => Promise<{ error: ValidationError | null }>; - - 'rule-add': ( - rule: Omit, - ) => Promise<{ error: ValidationError } | RuleEntity>; - - 'rule-update': < - PartialRule extends Partial> & - Pick, - >( - rule: PartialRule, - ) => Promise<{ error: ValidationError } | PartialRule>; - - 'rule-delete': (id: string) => Promise; - - 'rule-delete-all': ( - ids: string[], - ) => Promise<{ someDeletionsFailed: boolean }>; - - 'rule-apply-actions': (arg: { - transactions: TransactionEntity[]; - actions: Array; - }) => Promise; - - 'rule-add-payee-rename': (arg: { - fromNames: string[]; - to: string; - }) => Promise; - - 'rules-get': () => Promise; - - 'rule-get': (arg: { id: RuleEntity['id'] }) => Promise; - - 'rules-run': (arg: { - transaction: TransactionEntity; - }) => Promise; -} diff --git a/packages/loot-core/src/server/schedules/app.ts b/packages/loot-core/src/server/schedules/app.ts index 078b0e7376..2e08b629f9 100644 --- a/packages/loot-core/src/server/schedules/app.ts +++ b/packages/loot-core/src/server/schedules/app.ts @@ -16,6 +16,7 @@ import { getStatus, recurConfigToRSchedule, } from '../../shared/schedules'; +import { ScheduleEntity } from '../../types/models'; import { addTransactions } from '../accounts/sync'; import { createApp } from '../app'; import { runQuery as aqlQuery } from '../aql'; @@ -35,8 +36,6 @@ import { undoable } from '../undo'; import { Schedule as RSchedule } from '../util/rschedule'; import { findSchedules } from './find-schedules'; -import { SchedulesHandlers } from './types/handlers'; - // Utilities function zip(arr1, arr2) { @@ -185,7 +184,7 @@ async function checkIfScheduleExists(name, scheduleId) { export async function createSchedule({ schedule = null, conditions = [], -} = {}) { +} = {}): Promise { const scheduleId = schedule?.id || uuidv4(); const { date: dateCond } = extractScheduleConds(conditions); @@ -516,6 +515,17 @@ async function advanceSchedulesService(syncSuccess) { } } +export type SchedulesHandlers = { + 'schedule/create': typeof createSchedule; + 'schedule/update': typeof updateSchedule; + 'schedule/delete': typeof deleteSchedule; + 'schedule/skip-next-date': typeof skipNextDate; + 'schedule/post-transaction': typeof postTransactionForSchedule; + 'schedule/force-run-service': typeof advanceSchedulesService; + 'schedule/discover': typeof discoverSchedules; + 'schedule/get-upcoming-dates': typeof getUpcomingDates; +}; + // Expose functions to the client export const app = createApp(); diff --git a/packages/loot-core/src/server/schedules/find-schedules.ts b/packages/loot-core/src/server/schedules/find-schedules.ts index adabe2b2a1..3605e17735 100644 --- a/packages/loot-core/src/server/schedules/find-schedules.ts +++ b/packages/loot-core/src/server/schedules/find-schedules.ts @@ -13,8 +13,6 @@ import { fromDateRepr } from '../models'; import { conditionsToAQL } from '../transactions/transaction-rules'; import { Schedule as RSchedule } from '../util/rschedule'; -import { SchedulesHandlers } from './types/handlers'; - function takeDates(config) { const schedule = new RSchedule({ rrules: recurConfigToRSchedule(config) }); return schedule @@ -385,8 +383,7 @@ export async function findSchedules() { }, ); - const finalized: Awaited> = - []; + const finalized: Awaited> = []; for (const schedule of schedules) { finalized.push(await findStartDate(schedule)); } diff --git a/packages/loot-core/src/server/schedules/types/handlers.ts b/packages/loot-core/src/server/schedules/types/handlers.ts deleted file mode 100644 index 42218e1bd9..0000000000 --- a/packages/loot-core/src/server/schedules/types/handlers.ts +++ /dev/null @@ -1,38 +0,0 @@ -// @ts-strict-ignore -import { DiscoverScheduleEntity, ScheduleEntity } from '../../../types/models'; - -export interface SchedulesHandlers { - 'schedule/create': (arg: { - schedule: { - id?: string; - name?: string; - posts_transaction?: boolean; - }; - conditions: unknown[]; - }) => Promise; - - 'schedule/update': (schedule: { - schedule; - conditions?; - resetNextDate?: boolean; - }) => Promise; - - 'schedule/delete': (arg: { id: ScheduleEntity['id'] }) => Promise; - - 'schedule/skip-next-date': (arg: { - id: ScheduleEntity['id']; - }) => Promise; - - 'schedule/post-transaction': (arg: { - id: ScheduleEntity['id']; - }) => Promise; - - 'schedule/force-run-service': () => Promise; - - 'schedule/discover': () => Promise; - - 'schedule/get-upcoming-dates': (arg: { - config; - count: number; - }) => Promise; -} diff --git a/packages/loot-core/src/server/tools/app.ts b/packages/loot-core/src/server/tools/app.ts index 7b6736c078..6de36c68c4 100644 --- a/packages/loot-core/src/server/tools/app.ts +++ b/packages/loot-core/src/server/tools/app.ts @@ -1,16 +1,28 @@ // @ts-strict-ignore import { q } from '../../shared/query'; +import { TransactionEntity } from '../../types/models'; import { createApp } from '../app'; import { runQuery } from '../aql'; import * as db from '../db'; import { runMutator } from '../mutators'; import { batchUpdateTransactions } from '../transactions'; -import { ToolsHandlers } from './types/handlers'; +export type ToolsHandlers = { + 'tools/fix-split-transactions': typeof fixSplitTransactions; +}; export const app = createApp(); -app.method('tools/fix-split-transactions', async () => { +app.method('tools/fix-split-transactions', fixSplitTransactions); + +async function fixSplitTransactions(): Promise<{ + numBlankPayees: number; + numCleared: number; + numDeleted: number; + numTransfersFixed: number; + numNonParentErrorsFixed: number; + mismatchedSplits: TransactionEntity[]; +}> { // 1. Check for child transactions that have a blank payee, and set // the payee to whatever the parent has const blankPayeeRows = await db.all< @@ -119,4 +131,4 @@ app.method('tools/fix-split-transactions', async () => { numNonParentErrorsFixed: errorRows.length, mismatchedSplits, }; -}); +} diff --git a/packages/loot-core/src/server/tools/types/handlers.d.ts b/packages/loot-core/src/server/tools/types/handlers.d.ts deleted file mode 100644 index 2a210ebad2..0000000000 --- a/packages/loot-core/src/server/tools/types/handlers.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TransactionEntity } from '../../../types/models'; - -export interface ToolsHandlers { - 'tools/fix-split-transactions': () => Promise<{ - numBlankPayees: number; - numCleared: number; - numDeleted: number; - numTransfersFixed: number; - numNonParentErrorsFixed: number; - mismatchedSplits: TransactionEntity[]; - }>; -} diff --git a/packages/loot-core/src/types/handlers.d.ts b/packages/loot-core/src/types/handlers.d.ts index c0ec2a28c2..00eb461c25 100644 --- a/packages/loot-core/src/types/handlers.d.ts +++ b/packages/loot-core/src/types/handlers.d.ts @@ -1,15 +1,15 @@ import type { AccountHandlers } from '../server/accounts/app'; -import type { AdminHandlers } from '../server/admin/types/handlers'; +import type { AdminHandlers } from '../server/admin/app'; import type { BudgetHandlers } from '../server/budget/app'; -import type { DashboardHandlers } from '../server/dashboard/types/handlers'; -import type { FiltersHandlers } from '../server/filters/types/handlers'; -import type { NotesHandlers } from '../server/notes/types/handlers'; +import type { DashboardHandlers } from '../server/dashboard/app'; +import type { FiltersHandlers } from '../server/filters/app'; +import type { NotesHandlers } from '../server/notes/app'; import type { PayeesHandlers } from '../server/payees/app'; import type { PreferencesHandlers } from '../server/preferences/app'; -import type { ReportsHandlers } from '../server/reports/types/handlers'; -import type { RulesHandlers } from '../server/rules/types/handlers'; -import type { SchedulesHandlers } from '../server/schedules/types/handlers'; -import type { ToolsHandlers } from '../server/tools/types/handlers'; +import type { ReportsHandlers } from '../server/reports/app'; +import type { RulesHandlers } from '../server/rules/app'; +import type { SchedulesHandlers } from '../server/schedules/app'; +import type { ToolsHandlers } from '../server/tools/app'; import type { TransactionHandlers } from '../server/transactions/app'; import type { ApiHandlers } from './api-handlers'; diff --git a/packages/loot-core/src/types/models/index.d.ts b/packages/loot-core/src/types/models/index.d.ts index be8b943372..876b209597 100644 --- a/packages/loot-core/src/types/models/index.d.ts +++ b/packages/loot-core/src/types/models/index.d.ts @@ -16,4 +16,4 @@ export type * from './simplefin'; export type * from './transaction'; export type * from './transaction-filter'; export type * from './user'; -export type * from './userAccess'; +export type * from './user-access'; diff --git a/packages/loot-core/src/types/models/userAccess.ts b/packages/loot-core/src/types/models/user-access.d.ts similarity index 100% rename from packages/loot-core/src/types/models/userAccess.ts rename to packages/loot-core/src/types/models/user-access.d.ts diff --git a/packages/loot-core/src/types/models/user.ts b/packages/loot-core/src/types/models/user.d.ts similarity index 100% rename from packages/loot-core/src/types/models/user.ts rename to packages/loot-core/src/types/models/user.d.ts diff --git a/upcoming-release-notes/4650.md b/upcoming-release-notes/4650.md new file mode 100644 index 0000000000..a474d7071e --- /dev/null +++ b/upcoming-release-notes/4650.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Update server handlers to use the implementation function type instead of duplicating the function arguments/return types