Skip to content

Commit

Permalink
feat(api): Migrating to ULID instead of 64-bit integer IDs (#1740)
Browse files Browse the repository at this point in the history
* feat(api): Migrating to ULID instead of 64-bit integer IDs

This will make writing data in bulk easier in the future, it will also
make this data more portable between environments or between databases.

This also deprecates teller.io support.
  • Loading branch information
elliotcourant committed May 19, 2024
1 parent 2f404d2 commit 8b673b6
Show file tree
Hide file tree
Showing 227 changed files with 10,641 additions and 13,099 deletions.
7 changes: 0 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -222,20 +222,13 @@ add_custom_target(
DEPENDS ${MONETR_EXECUTABLE}
)

add_custom_target(
development.migrate
COMMAND ${CMAKE_Go_COMPILER} run ${MONETR_CLI_PKG} database migrate
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)

################################################################################
# TESTING #
################################################################################

enable_testing()
option(BUILD_TESTING "Build the testing tree." OFF)


include(GolangTestUtils)

if(BUILD_TESTING)
Expand Down
4 changes: 2 additions & 2 deletions cmake/GolangUtils.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ endfunction()

# mockgen for local development
set(MOCKGEN_EXECUTABLE ${GO_BIN_DIR}/mockgen)
set(MOCKGEN_VERSION "v1.6.0")
set(MOCKGEN_VERSION "v0.4.0")
go_install(
OUTPUT ${MOCKGEN_EXECUTABLE}
PACKAGE "github.com/golang/mock/mockgen"
PACKAGE "go.uber.org/mock/mockgen"
VERSION ${MOCKGEN_VERSION}
)
58 changes: 29 additions & 29 deletions docs/src/pages/documentation/development/api/funding_schedules.mdx

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ require (
github.com/go-pg/pg/v10 v10.12.0
github.com/gocraft/work v0.5.1
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/mock v1.6.0
github.com/gomodule/redigo v1.8.9
github.com/google/uuid v1.6.0
github.com/jarcoal/httpmock v1.3.1
github.com/klauspost/cpuid/v2 v2.2.7
github.com/labstack/echo/v4 v4.11.4
github.com/mileusna/useragent v1.3.4
github.com/nyaruka/phonenumbers v1.3.4
github.com/oklog/ulid/v2 v2.1.0
github.com/pkg/errors v0.9.1
github.com/plaid/plaid-go/v20 v20.1.0
github.com/prometheus/client_golang v1.17.0
Expand Down Expand Up @@ -132,8 +132,9 @@ require (
github.com/yuin/gopher-lua v1.1.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/mock v0.4.0
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/sync v0.6.0 // indirect
Expand Down
6 changes: 5 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down Expand Up @@ -351,6 +350,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/nyaruka/phonenumbers v1.3.4 h1:bF1Wdh++fxw09s3surhVeBhXEcUKG07pHeP8HQXqjn8=
github.com/nyaruka/phonenumbers v1.3.4/go.mod h1:Ut+eFwikULbmCenH6InMKL9csUNLyxHuBLyfkpum11s=
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
Expand All @@ -359,6 +360,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
Expand Down Expand Up @@ -506,6 +508,8 @@ go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
Expand Down
12 changes: 6 additions & 6 deletions interface/src/components/MSelectSpending.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ export default function MSelectSpending(props: MSelectSpendingProps): JSX.Elemen

const freeToUse: SpendingOption = {
label: 'Free-To-Use',
value: -1,
value: '',
spending: new Spending({
spendingId: -1,
spendingId: '',
// It is possible for the "safe" balance to not be present when switching bank accounts. This is a pseudo race
// condition. Instead we want to gracefully handle the value not being present initially, and print a nicer string
// until the balance is loaded.
Expand Down Expand Up @@ -90,7 +90,7 @@ export default function MSelectSpending(props: MSelectSpendingProps): JSX.Elemen
// some other select has already picked the safe to spend option. We need to omit that
// from our result set.
if (props.excludeFrom && !excludedFrom) {
return item.value !== -1;
return item.value !== '';
}

return true;
Expand All @@ -99,8 +99,8 @@ export default function MSelectSpending(props: MSelectSpendingProps): JSX.Elemen
const value = formikContext.values[props.name];
const current = options.find(item => item.value === (value ?? -1));

function onSelect(newValue: { label: string, value: number }) {
if (newValue.value === -1) {
function onSelect(newValue: { label: string, value: string }) {
if (newValue.value === '') {
return formikContext.setFieldValue(props.name, null);
}

Expand All @@ -123,7 +123,7 @@ export default function MSelectSpending(props: MSelectSpendingProps): JSX.Elemen

interface SpendingOption {
readonly label: string;
readonly value: number | null;
readonly value: string | null;
readonly spending: Spending | null;
}

Expand Down
2 changes: 1 addition & 1 deletion interface/src/hooks/balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
import { useSelectedBankAccountId } from '@monetr/interface/hooks/bankAccounts';
import Balance from '@monetr/interface/models/Balance';

export function useBalance(bankAccountId: number): Balance | null {
export function useBalance(bankAccountId: string): Balance | null {
const result = useQuery<Partial<Balance>>([`/bank_accounts/${ bankAccountId }/balances`]);
return result?.data && new Balance(result?.data);
}
Expand Down
32 changes: 11 additions & 21 deletions interface/src/hooks/bankAccount.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ describe('bank account hooks', () => {
describe('useSelectedBankAccount', () => {
it('valid URL', async () => {
server.use(
rest.get('/api/bank_accounts/12', (_req, res, ctx) => {
rest.get('/api/bank_accounts/bac_01hy4rcmadc01d2kzv7vynbxxx', (_req, res, ctx) => {
return res(ctx.json({
'bankAccountId': 12,
'linkId': 4,
'bankAccountId': 'bac_01hy4rcmadc01d2kzv7vynbxxx', // 12,
'linkId': 'link_01hy4rbb1gjdek7h2xmgy5pnwk', // 4
'availableBalance': 48635,
'currentBalance': 48635,
'mask': '2982',
Expand All @@ -27,32 +27,22 @@ describe('bank account hooks', () => {
);

{ // Make sure use selected bank account works.
const world = testRenderHook(useSelectedBankAccount, { initialRoute: '/bank/12/expenses' });
const world = testRenderHook(useSelectedBankAccount, {
initialRoute: '/bank/bac_01hy4rcmadc01d2kzv7vynbxxx/expenses',
});
expect(world.result.current.data).not.toBeDefined();
expect(world.result.current.isLoading).toBeTruthy();
await world.waitFor(() => expect(world.result.current.isSuccess).toBeTruthy());
expect(world.result.current.data.bankAccountId).toBe(12);
expect(world.result.current.data.bankAccountId).toBe('bac_01hy4rcmadc01d2kzv7vynbxxx');
}

{ // Then make sure that useSelectedBankAccountId works
const world = testRenderHook(useSelectedBankAccountId, { initialRoute: '/bank/12/expenses' });
const world = testRenderHook(useSelectedBankAccountId, { initialRoute:
'/bank/bac_01hy4rcmadc01d2kzv7vynbxxx/expenses',
});
expect(world.result.current).toBeUndefined();
await world.waitFor(() => expect(world.result.current).toBeDefined());
expect(world.result.current).toBe(12);
}
});

it('invalid url', async () => {
{ // useSelectedBankAccount
const { result } = testRenderHook(useSelectedBankAccount, { initialRoute: '/bank/bad/expenses' });
expect(result.error).toBeDefined();
expect(result.error.message).toBe('invalid bank account ID specified: "bad" is not a valid bank account ID');
}

{ // useSelectedBankAccountId
const { result } = testRenderHook(useSelectedBankAccountId, { initialRoute: '/bank/bad/expenses' });
expect(result.error).toBeDefined();
expect(result.error.message).toBe('invalid bank account ID specified: "bad" is not a valid bank account ID');
expect(world.result.current).toBe('bac_01hy4rcmadc01d2kzv7vynbxxx');
}
});

Expand Down
6 changes: 3 additions & 3 deletions interface/src/hooks/bankAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function useBankAccounts(): UseQueryResult<Array<BankAccount>> {
}

export interface CreateBankAccountRequest {
linkId: number;
linkId: string;
name: string;
mask?: string;
availableBalance: number;
Expand Down Expand Up @@ -54,7 +54,7 @@ export function useCreateBankAccount(): (_bankAccount: CreateBankAccountRequest)
export function useSelectedBankAccount(): UseQueryResult<BankAccount | undefined> {
const queryClient = useQueryClient();
const match = useMatch('/bank/:bankId/*');
const bankAccountId = +match?.params?.bankId || null;
const bankAccountId = match?.params?.bankId || null;

// If we do not have a valid numeric bank account ID, but an ID was specified then something is wrong.
if (!bankAccountId && match?.params?.bankId) {
Expand All @@ -75,7 +75,7 @@ export function useSelectedBankAccount(): UseQueryResult<BankAccount | undefined
);
}

export function useSelectedBankAccountId(): number | undefined {
export function useSelectedBankAccountId(): string | undefined {
const { data: bankAccount } = useSelectedBankAccount();
return bankAccount?.bankAccountId;
}
Expand Down
10 changes: 5 additions & 5 deletions interface/src/hooks/forecast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { SpendingType } from '@monetr/interface/models/Spending';
import request from '@monetr/interface/util/request';

interface SpendingBareMinimum {
bankAccountId: number;
bankAccountId: string;
nextRecurrence: Date;
spendingType: SpendingType;
fundingScheduleId: number;
fundingScheduleId: string;
targetAmount: number;
recurrenceRule: string | null,
}
Expand All @@ -27,7 +27,7 @@ export function useSpendingForecast(): (spending: SpendingBareMinimum) => Promis
};
}

export function useNextFundingForecast(fundingScheduleId: number): UseQueryResult<number> {
export function useNextFundingForecast(fundingScheduleId: string): UseQueryResult<number> {
const selectedBankAccountId = useSelectedBankAccountId();
return useQuery<Partial<{ nextContribution: number }>, unknown, number>(
[
Expand Down Expand Up @@ -84,7 +84,7 @@ export class SpendingEvent {
date: Date;
funding: Array<FundingEvent>;
rollingAllocation: number;
spendingId: number;
spendingId: string;
transactionAmount: number;

constructor(data?: Partial<SpendingEvent>) {
Expand All @@ -98,7 +98,7 @@ export class SpendingEvent {

export class FundingEvent {
date: Date;
fundingScheduleId: number;
fundingScheduleId: string;
originalDate: Date;
weekendAvoided: boolean;

Expand Down
8 changes: 4 additions & 4 deletions interface/src/hooks/fundingSchedules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe('funding schedule hooks', () => {
bankAccountId: 12,
description: 'something',
name: 'Elliot\'s Contribution',
nextOccurrence: parseJSON('2023-07-31T05:00:00Z'),
nextRecurrence: parseJSON('2023-07-31T05:00:00Z'),
ruleset: 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=15,-1',
estimatedDeposit: null,
excludeWeekends: true,
Expand Down Expand Up @@ -188,7 +188,7 @@ describe('funding schedule hooks', () => {
bankAccountId: 12,
description: 'something',
name: 'Elliot\'s Contribution',
nextOccurrence: parseJSON('2023-07-31T05:00:00Z'),
nextRecurrence: parseJSON('2023-07-31T05:00:00Z'),
ruleset: 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=15,-1',
estimatedDeposit: null,
excludeWeekends: true,
Expand Down Expand Up @@ -248,7 +248,7 @@ describe('funding schedule hooks', () => {
bankAccountId: 12,
description: 'something',
name: 'Elliot\'s Contribution',
nextOccurrence: parseJSON('2023-07-31T05:00:00Z'),
nextRecurrence: parseJSON('2023-07-31T05:00:00Z'),
ruleset: 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=15,-1',
estimatedDeposit: null,
excludeWeekends: true,
Expand Down Expand Up @@ -292,7 +292,7 @@ describe('funding schedule hooks', () => {
bankAccountId: 12,
description: 'something',
name: 'Elliot\'s Contribution',
nextOccurrence: parseJSON('2023-07-31T05:00:00Z'),
nextRecurrence: parseJSON('2023-07-31T05:00:00Z'),
ruleset: 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=15,-1',
estimatedDeposit: null,
excludeWeekends: true,
Expand Down
6 changes: 3 additions & 3 deletions interface/src/hooks/fundingSchedules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ export function useFundingSchedulesSink(): UseQueryResult<Array<FundingSchedule>
export function useNextFundingDate(): string | null {
const { data: funding } = useFundingSchedulesSink();
const date = funding
?.sort((a, b) => isBefore(a.nextOccurrence, b.nextOccurrence) ? 1 : -1)
?.sort((a, b) => isBefore(a.nextRecurrence, b.nextRecurrence) ? 1 : -1)
.pop();

if (date) {
return format(date.nextOccurrence, 'M/dd');
return format(date.nextRecurrence, 'M/dd');
}

return null;
}

export function useFundingSchedule(fundingScheduleId: number | null): UseQueryResult<FundingSchedule | undefined, unknown> {
export function useFundingSchedule(fundingScheduleId: string | null): UseQueryResult<FundingSchedule | undefined, unknown> {
const selectedBankAccountId = useSelectedBankAccountId();
return useQuery<Partial<FundingSchedule>, unknown, FundingSchedule | null>(
[`/bank_accounts/${selectedBankAccountId}/funding_schedules/${fundingScheduleId}`],
Expand Down
6 changes: 3 additions & 3 deletions interface/src/hooks/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function useLinks(): UseQueryResult<Array<Link>> {
});
}

export function useLink(linkId: number | null): UseQueryResult<Link> {
export function useLink(linkId: string | null): UseQueryResult<Link> {
const queryClient = useQueryClient();
return useQuery<Partial<Link>, unknown, Link>(
[`/links/${linkId}`],
Expand Down Expand Up @@ -78,9 +78,9 @@ export function useCreateLink(): (_link: CreateLinkRequest) => Promise<Link> {
return mutate.mutateAsync;
}

export function useRemoveLink(): (_linkId: number) => Promise<void> {
export function useRemoveLink(): (_linkId: string) => Promise<void> {
const queryClient = useQueryClient();
return async function (linkId: number): Promise<void> {
return async function (linkId: string): Promise<void> {
return request()
.delete(`/links/${linkId}`)
.then(() => void Promise.all([
Expand Down
Loading

0 comments on commit 8b673b6

Please sign in to comment.