Skip to content

Commit

Permalink
chore: use PartialReturn type
Browse files Browse the repository at this point in the history
  • Loading branch information
UncleSamtoshi committed Oct 26, 2023
1 parent 21f0dbd commit 3d1767f
Show file tree
Hide file tree
Showing 19 changed files with 210 additions and 185 deletions.
1 change: 1 addition & 0 deletions core/api/dev/apollo-federation/supergraph.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,7 @@ type QuizCompletedPayload
{
errors: [Error!]!
quiz: Quiz
rewardPaid: Boolean
}

type QuizQuestion
Expand Down
24 changes: 19 additions & 5 deletions core/api/src/app/index.types.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
type PartialResult<T> = {
result: T | null
error?: ApplicationError
partialResult: true
}
type PartialResultType =
(typeof import("./partial-result").PartialResultType)[keyof typeof import("./partial-result").PartialResultType]

type PartialResult<T> =
| {
result: T
error?: undefined
type: typeof import("./partial-result").PartialResultType.Ok
}
| {
result: T
error: ApplicationError
type: typeof import("./partial-result").PartialResultType.Partial
}
| {
result: null
error: ApplicationError
type: typeof import("./partial-result").PartialResultType.Err
}

type ValueOf<T> = T[keyof T]

Expand Down
12 changes: 9 additions & 3 deletions core/api/src/app/partial-result.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
export const PartialResult = {
ok: <T>(result: T): PartialResult<T> => ({
result,
partialResult: true,
type: PartialResultType.Ok,
}),
partial: <T>(result: T, error: ApplicationError): PartialResult<T> => ({
result,
error,
partialResult: true,
type: PartialResultType.Partial,
}),
err: <T>(error: ApplicationError): PartialResult<T> => ({
result: null,
error,
partialResult: true,
type: PartialResultType.Err,
}),
}

export const PartialResultType = {
Partial: "Partial",
Ok: "Ok",
Err: "Err",
} as const
122 changes: 79 additions & 43 deletions core/api/src/app/payments/add-earn.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PartialResult } from "../partial-result"

import { intraledgerPaymentSendWalletIdForBtcWallet } from "./send-intraledger"

import { getRewardsConfig, OnboardingEarn } from "@/config"
Expand Down Expand Up @@ -30,40 +32,40 @@ export const addEarn = async ({
quizQuestionId: string
accountId: string
}): Promise<
| {
quizQuestion: Quiz
rewardPaymentError?: ApplicationError
}
| ApplicationError
PartialResult<{
quiz: Quiz
rewardPaid: boolean
}>
> => {
const accountId = checkedToAccountId(accountIdRaw)
if (accountId instanceof Error) return accountId
if (accountId instanceof Error) return PartialResult.err(accountId)

const rewardsConfig = getRewardsConfig()

// TODO: quizQuestionId checkedFor
const quizQuestionId = quizQuestionIdString as QuizQuestionId

const amount = OnboardingEarn[quizQuestionId]
if (!amount) return new InvalidQuizQuestionIdError()
if (!amount) return PartialResult.err(new InvalidQuizQuestionIdError())

const funderWalletId = await getFunderWalletId()
const funderWallet = await WalletsRepository().findById(funderWalletId)
if (funderWallet instanceof Error) return funderWallet
if (funderWallet instanceof Error) return PartialResult.err(funderWallet)
const funderAccount = await AccountsRepository().findById(funderWallet.accountId)
if (funderAccount instanceof Error) return funderAccount
if (funderAccount instanceof Error) return PartialResult.err(funderAccount)

const recipientAccount = await AccountsRepository().findById(accountId)
if (recipientAccount instanceof Error) return recipientAccount
if (recipientAccount instanceof Error) return PartialResult.err(recipientAccount)

const user = await UsersRepository().findById(recipientAccount.kratosUserId)
if (user instanceof Error) return user
if (user instanceof Error) return PartialResult.err(user)

const isFirstTimeAnsweringQuestion =
await RewardsRepository(accountId).add(quizQuestionId)
if (isFirstTimeAnsweringQuestion instanceof Error) return isFirstTimeAnsweringQuestion
if (isFirstTimeAnsweringQuestion instanceof Error)
return PartialResult.err(isFirstTimeAnsweringQuestion)

const quizQuestion: Quiz = {
const quiz: Quiz = {
id: quizQuestionId,
amount: amount,
completed: true,
Expand All @@ -74,53 +76,78 @@ export const addEarn = async ({
).authorize(user.phoneMetadata)

if (validatedPhoneMetadata instanceof Error) {
return {
quizQuestion,
rewardPaymentError: new InvalidPhoneForRewardError(validatedPhoneMetadata.name),
}
return PartialResult.partial(
{
quiz,
rewardPaid: false,
},
new InvalidPhoneForRewardError(validatedPhoneMetadata.name),
)
}

const accountIP = await AccountsIpsRepository().findLastByAccountId(recipientAccount.id)
if (accountIP instanceof Error) return accountIP
if (accountIP instanceof Error)
return PartialResult.partial(
{
quiz,
rewardPaid: false,
},
new InvalidPhoneForRewardError(accountIP),
)

const validatedIPMetadata = IPMetadataAuthorizer(
rewardsConfig.ipMetadataValidationSettings,
).authorize(accountIP.metadata)

if (validatedIPMetadata instanceof Error) {
if (validatedIPMetadata instanceof MissingIPMetadataError)
return {
quizQuestion,
rewardPaymentError: new InvalidIpMetadataError(validatedIPMetadata),
}
return PartialResult.partial(
{
quiz,
rewardPaid: false,
},
new InvalidIpMetadataError(validatedIPMetadata),
)

if (validatedIPMetadata instanceof UnauthorizedIPError)
return {
quizQuestion,
rewardPaymentError: validatedIPMetadata,
}

return {
quizQuestion,
rewardPaymentError: new UnknownRepositoryError("add earn error"),
}
return PartialResult.partial(
{
quiz,
rewardPaid: false,
},
validatedIPMetadata,
)

return PartialResult.partial(
{
quiz,
rewardPaid: false,
},
new UnknownRepositoryError("add earn error"),
)
}

const recipientWallets = await WalletsRepository().listByAccountId(accountId)
if (recipientWallets instanceof Error)
return {
quizQuestion,
rewardPaymentError: recipientWallets,
}
return PartialResult.partial(
{
quiz,
rewardPaid: false,
},
recipientWallets,
)

const recipientBtcWallet = recipientWallets.find(
(wallet) => wallet.currency === WalletCurrency.Btc,
)
if (recipientBtcWallet === undefined)
return {
quizQuestion,
rewardPaymentError: new NoBtcWalletExistsForAccountError(),
}
return PartialResult.partial(
{
quiz,
rewardPaid: false,
},
new NoBtcWalletExistsForAccountError(),
)

const recipientWalletId = recipientBtcWallet.id

Expand All @@ -132,10 +159,19 @@ export const addEarn = async ({
senderAccount: funderAccount,
})

return {
quizQuestion,
rewardPaymentError: payment instanceof Error ? payment : undefined,
}
if (payment instanceof Error)
return PartialResult.partial(
{
quiz,
rewardPaid: false,
},
payment,
)

return PartialResult.ok({
quiz,
rewardPaid: true,
})
}

export const isAccountEligibleForEarnPayment = async ({
Expand Down
3 changes: 0 additions & 3 deletions core/api/src/domain/bitcoin/lightning/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ export class DestinationMissingDependentFeatureError extends LightningServiceErr
export class LookupPaymentTimedOutError extends LightningServiceError {
level = ErrorLevel.Critical
}
export class InvalidFeeProbeStateError extends LightningServiceError {
level = ErrorLevel.Critical
}

export class UnknownRouteNotFoundError extends LightningServiceError {
level = ErrorLevel.Critical
Expand Down
1 change: 0 additions & 1 deletion core/api/src/domain/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export class CouldNotFindLnPaymentFromHashError extends CouldNotFindError {
export class CouldNotFindAccountFromIdError extends CouldNotFindError {}
export class CouldNotFindAccountFromUsernameError extends CouldNotFindError {}
export class CouldNotFindAccountFromPhoneError extends CouldNotFindError {}
export class CouldNotFindTransactionsForAccountError extends CouldNotFindError {}
export class CouldNotFindAccountFromKratosIdError extends CouldNotFindError {}

export class RewardAlreadyPresentError extends DomainError {}
Expand Down
5 changes: 0 additions & 5 deletions core/api/src/graphql/error-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,6 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => {
message = `The associated lightning invoice could not be found.`
return new NotFoundError({ message, logger: baseLogger })

case "CouldNotFindTransactionsForAccountError":
message = "No transactions found for your account."
return new NotFoundError({ message, logger: baseLogger })

case "CouldNotFindUserFromPhoneError":
message = `User does not exist for phone ${error.message}`
return new NotFoundError({ message, logger: baseLogger })
Expand Down Expand Up @@ -591,7 +587,6 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => {
case "BigIntConversionError":
case "BigIntFloatConversionError":
case "SafeWrapperError":
case "InvalidFeeProbeStateError":
case "LookupPaymentTimedOutError":
case "InvalidPubKeyError":
case "SkipProbeForPubkeyError":
Expand Down
27 changes: 12 additions & 15 deletions core/api/src/graphql/public/root/mutation/ln-invoice-fee-probe.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { normalizePaymentAmount } from "../../../shared/root/mutation"

import { InvalidFeeProbeStateError } from "@/domain/bitcoin/lightning"

import { Payments } from "@/app"

import { GT } from "@/graphql/index"
import WalletId from "@/graphql/shared/types/scalar/wallet-id"
import SatAmountPayload from "@/graphql/public/types/payload/sat-amount"
import LnPaymentRequest from "@/graphql/shared/types/scalar/ln-payment-request"
import { mapAndParseErrorForGqlResponse } from "@/graphql/error-map"
import { PartialResultType } from "@/app/partial-result"

const LnInvoiceFeeProbeInput = GT.Input({
name: "LnInvoiceFeeProbeInput",
Expand Down Expand Up @@ -43,31 +42,29 @@ const LnInvoiceFeeProbeMutation = GT.Field<
if (paymentRequest instanceof Error)
return { errors: [{ message: paymentRequest.message }] }

const { result: feeSatAmount, error } =
await Payments.getLightningFeeEstimationForBtcWallet({
walletId,
uncheckedPaymentRequest: paymentRequest,
})
const {
result: feeSatAmount,
error,
type,
} = await Payments.getLightningFeeEstimationForBtcWallet({
walletId,
uncheckedPaymentRequest: paymentRequest,
})

if (feeSatAmount !== null && error instanceof Error) {
if (type === PartialResultType.Partial) {
error
return {
errors: [mapAndParseErrorForGqlResponse(error)],
...normalizePaymentAmount(feeSatAmount),
}
}

if (error instanceof Error) {
if (type === PartialResultType.Err) {
return {
errors: [mapAndParseErrorForGqlResponse(error)],
}
}

if (feeSatAmount === null) {
return {
errors: [mapAndParseErrorForGqlResponse(new InvalidFeeProbeStateError())],
}
}

return {
errors: [],
...normalizePaymentAmount(feeSatAmount),
Expand Down
Loading

0 comments on commit 3d1767f

Please sign in to comment.