Skip to content

Commit

Permalink
feat!: use API v4 (#1456)
Browse files Browse the repository at this point in the history
With this, the frontend uses API v4. API v4 removes computed endpoints
from resource collection endpoints and exposes these at new endpoints.

This requires a few more API calls from frontend, but greatly improves
performance since we only request data when it's needed.

BREAKING CHANGE: With this change, the frontend requires backend version v4.2.2.

Co-authored-by: Fynn Heintz <fynn.heintz@gmail.com>
  • Loading branch information
morremeyer and malfynnction authored Jan 12, 2024
1 parent e0e03ab commit cb33066
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 33 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Check the table below for the minimal required [backend](https://github.com/enve
| 0.9.0 | v0.22.0 |
| 1.9.0 | v2.0.0 |
| 2.0.0 | v3.21.1 |
| 3.0.0 | v4.2.2 |

## Contributing

Expand Down
1 change: 1 addition & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ volumes:
services:
backend:
image: ghcr.io/envelope-zero/backend:v4.2.2
user: root
volumes:
- ez-dev-data:/data
ports:
Expand Down
1 change: 1 addition & 0 deletions docker-compose-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ volumes:
services:
backend:
image: ghcr.io/envelope-zero/backend:v4.2.2
user: root
volumes:
- ez-production-data:/data
environment:
Expand Down
1 change: 1 addition & 0 deletions docker-compose-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ version: '3'
services:
backend:
image: ghcr.io/envelope-zero/backend:v4.2.2
user: root
ports:
- 8081:8080
environment:
Expand Down
53 changes: 40 additions & 13 deletions src/components/OwnAccountsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
PlusCircleIcon,
PlusIcon,
} from '@heroicons/react/24/outline'
import { api } from '../lib/api/base'
import { api, get } from '../lib/api/base'
import { formatMoney } from '../lib/format'
import { safeName } from '../lib/name-helper'
import LoadingSpinner from './LoadingSpinner'
Expand Down Expand Up @@ -39,7 +39,29 @@ const OwnAccountsList = ({ budget }: Props) => {
accountApi
.getAll(budget, { external: false, archived: Boolean(archived) })
.then(data => {
setAccounts(data)
// Create list of all IDs
const ids = data.map((account: Account) => {
return account.id
})

// Get the computed data for all accounts and add it to the accounts
return get(
data[0].links.computedData,
JSON.stringify({
time: new Date(),
ids: ids,
})
).then(computedData => {
return data.map((account: Account) => ({
...account,
computedData: computedData.find(
(e: Account['computedData']) => e?.id == account.id
),
}))
})
})
.then(accounts => {
setAccounts(accounts)
setIsLoading(false)
setError('')
})
Expand Down Expand Up @@ -118,17 +140,22 @@ const OwnAccountsList = ({ budget }: Props) => {
{account.note}
</p>
) : null}
<div
className={`${
Number(account.balance) >= 0
? 'text-lime-600'
: 'text-red-600'
} mt-2 text-lg`}
>
<strong>
{formatMoney(account.balance, budget.currency)}
</strong>
</div>
{account.computedData ? (
<div
className={`${
Number(account.computedData.balance) >= 0
? 'text-lime-600'
: 'text-red-600'
} mt-2 text-lg`}
>
<strong>
{formatMoney(
account.computedData.balance,
budget.currency
)}
</strong>
</div>
) : null}
</Link>
</li>
))}
Expand Down
41 changes: 27 additions & 14 deletions src/components/TransactionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import { useParams, useNavigate, Link, useSearchParams } from 'react-router-dom'
import { LockClosedIcon } from '@heroicons/react/20/solid'
import { DocumentDuplicateIcon, TrashIcon } from '@heroicons/react/24/outline'
import { api } from '../lib/api/base'
import { api, get } from '../lib/api/base'
import { UUID } from '../types'
import {
dateFromIsoString,
Expand All @@ -22,6 +22,7 @@ import {
Envelope,
Category,
GroupedEnvelopes,
RecentEnvelope,
} from '../types'
import LoadingSpinner from './LoadingSpinner'
import Error from './Error'
Expand Down Expand Up @@ -61,7 +62,7 @@ const TransactionForm = ({ budget, setNotification }: Props) => {
useState<UnpersistedAccount>()
const [destinationAccountToCreate, setDestinationAccountToCreate] =
useState<UnpersistedAccount>()
const [recentEnvelopes, setRecentEnvelopes] = useState([] as UUID[])
const [recentEnvelopes, setRecentEnvelopes] = useState<RecentEnvelope[]>([])

const isPersisted =
typeof transactionId !== 'undefined' && transactionId !== 'new'
Expand Down Expand Up @@ -313,18 +314,27 @@ const TransactionForm = ({ budget, setNotification }: Props) => {
destinationAccountId: account.id,
}

// Suggest the first of the recentEnvelopes as the Envelope
// to use for this transaction
if (
account.external &&
!transaction.envelopeId &&
account.recentEnvelopes?.length
) {
setRecentEnvelopes(account.recentEnvelopes)
valuesToUpdate.envelopeId = account.recentEnvelopes[0]
let envelopePromises = []

// Verify if we need to propose an envelope
if (account.external && !transaction.envelopeId) {
// Fetch the recent envelopes and suggest the first one
// as the Envelope to use for this transaction
envelopePromises.push(
get(account.links.recentEnvelopes).then(data => {
setRecentEnvelopes(data)
if (data.length) {
valuesToUpdate.envelopeId = data[0].id
}
})
)
} else if (!account.external) {
setRecentEnvelopes([])
}

setTransaction({ ...transaction, ...valuesToUpdate })
Promise.all(envelopePromises).then(() =>
setTransaction({ ...transaction, ...valuesToUpdate })
)
}
}}
value={
Expand All @@ -341,8 +351,11 @@ const TransactionForm = ({ budget, setNotification }: Props) => {
groups={[
{
title: t('transactions.recentEnvelopes'),
items: envelopes.filter(envelope =>
recentEnvelopes.includes(envelope.id)
items: recentEnvelopes.map(
recentEnvelope =>
envelopes.find(
envelope => envelope.id === recentEnvelope.id
) as Envelope
),
},
...groupedEnvelopes,
Expand Down
11 changes: 8 additions & 3 deletions src/lib/api/base.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { ApiObject, UUID, Budget, FilterOptions } from '../../types'
import { checkStatus, parseJSON } from '../fetch-helper'

const endpoint = window.location.origin + '/api/v3'
const endpoint = window.location.origin + '/api/v4'

const getApiInfo = async () => {
return fetch(endpoint).then(checkStatus).then(parseJSON)
}

const get = async (url: string) => {
return fetch(url)
const get = async (url: string, body?: BodyInit) => {
// HTTP GET supports request body, but JS fetch does not
// Therefore, the backend exposes certain endpoints as
// HTTP POST
const method = typeof body === 'undefined' ? 'GET' : 'POST'

return fetch(url, { method: method, body: body })
.then(checkStatus)
.then(parseJSON)
.then(data => data.data)
Expand Down
13 changes: 10 additions & 3 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ export type BudgetApiConnection = {

export type Account = UnpersistedAccount &
ApiObject & {
balance: string
reconciledBalance: string
budgetId: UUID
recentEnvelopes: UUID[]
computedData?: {
id: string
balance: string
reconciledBalance: string
}
}

export type ApiResponse<T> = {
Expand Down Expand Up @@ -178,3 +180,8 @@ export type Theme = 'dark' | 'light' | 'default'
export type QuickAllocationMode =
| 'ALLOCATE_LAST_MONTH_BUDGET'
| 'ALLOCATE_LAST_MONTH_SPEND'

export type RecentEnvelope = {
name: string
id: UUID
}

0 comments on commit cb33066

Please sign in to comment.