diff --git a/src/app/dashboard/components/server/latest-invoices/latest-invoices-controller.ts b/src/app/dashboard/components/server/latest-invoices/latest-invoices-controller.ts
index 45bde22..8fbbde9 100644
--- a/src/app/dashboard/components/server/latest-invoices/latest-invoices-controller.ts
+++ b/src/app/dashboard/components/server/latest-invoices/latest-invoices-controller.ts
@@ -1,5 +1,5 @@
import fetchCustomerInvoicesUsecase from "@/feature/core/customer-invoice/domain/usecase/fetch-customer-invoices-usecase";
-export default function latestInvoicesController() {
- return fetchCustomerInvoicesUsecase()
+export default async function latestInvoicesController() {
+ return await fetchCustomerInvoicesUsecase()
}
\ No newline at end of file
diff --git a/src/app/dashboard/components/server/latest-invoices/latest-invoices.tsx b/src/app/dashboard/components/server/latest-invoices/latest-invoices.tsx
index 09396cd..696af73 100644
--- a/src/app/dashboard/components/server/latest-invoices/latest-invoices.tsx
+++ b/src/app/dashboard/components/server/latest-invoices/latest-invoices.tsx
@@ -2,12 +2,15 @@ import CreateRandomInvoiceContainer from '@/app/dashboard/components/client/crea
import latestInvoicesController from '@/app/dashboard/components/server/latest-invoices/latest-invoices-controller';
import { ArrowPathIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
+import { isLeft } from 'fp-ts/lib/Either';
import Image from 'next/image';
export default async function LatestInvoices() {
const latestInvoices = await latestInvoicesController();
- const invoices = latestInvoices.map((invoice, i) => {
+ if (isLeft(latestInvoices)) return
Error
+
+ const invoices = latestInvoices.right.map((invoice, i) => {
return (
{
/* ------------------------------- Attributes ------------------------------- */
private readonly BASE_FAILURE_MESSAGE = "failure";
@@ -15,8 +15,12 @@ export default abstract class BaseFailure {
message = this.BASE_FAILURE_MESSAGE;
/* -------------------------------------------------------------------------- */
- constructor(key: string) {
+ metadata: META_DATA | undefined;
+
+ /* -------------------------------------------------------------------------- */
+ constructor(key: string, metadata?: META_DATA) {
this.message = makeFailureMessage(this.message, key);
+ this.metadata = metadata ?? undefined
}
/* -------------------------------------------------------------------------- */
}
diff --git a/src/feature/common/failures/dev/arguments-failure.ts b/src/feature/common/failures/dev/arguments-failure.ts
index ed4e2c9..11d01c2 100644
--- a/src/feature/common/failures/dev/arguments-failure.ts
+++ b/src/feature/common/failures/dev/arguments-failure.ts
@@ -3,10 +3,10 @@ import BaseDevFailure from "@/feature/common/failures/dev/base-dev-failure";
/**
* Failure for needed arguments in a method but sent wrong one
*/
-export default class ArgumentsFailure extends BaseDevFailure {
+export default class ArgumentsFailure
extends BaseDevFailure {
/* ------------------------------- Constructor ------------------------------ */
- constructor() {
- super("arguments");
+ constructor(metadata?: META_DATA) {
+ super("arguments", metadata);
}
/* -------------------------------------------------------------------------- */
}
diff --git a/src/feature/common/failures/dev/base-dev-failure.ts b/src/feature/common/failures/dev/base-dev-failure.ts
index aaf8142..ad404d1 100644
--- a/src/feature/common/failures/dev/base-dev-failure.ts
+++ b/src/feature/common/failures/dev/base-dev-failure.ts
@@ -1,3 +1,3 @@
import BaseFailure from "@/feature/common/failures/base-failure";
-export default abstract class BaseDevFailure extends BaseFailure {}
+export default abstract class BaseDevFailure extends BaseFailure {}
diff --git a/src/feature/common/failures/dev/dependency-failure.ts b/src/feature/common/failures/dev/dependency-failure.ts
index 86b49cf..6f16f8f 100644
--- a/src/feature/common/failures/dev/dependency-failure.ts
+++ b/src/feature/common/failures/dev/dependency-failure.ts
@@ -3,8 +3,8 @@ import BaseDevFailure from "@/feature/common/failures/dev/base-dev-failure";
/**
* This is a failure of not having specific dependency
*/
-export default class DependencyFailure extends BaseDevFailure {
- constructor() {
- super("DependencyFailure");
+export default class DependencyFailure extends BaseDevFailure {
+ constructor(metadata: META_DATA) {
+ super("DependencyFailure", metadata);
}
}
diff --git a/src/feature/common/failures/network-failure.ts b/src/feature/common/failures/network-failure.ts
index 3268fc3..192bdf5 100644
--- a/src/feature/common/failures/network-failure.ts
+++ b/src/feature/common/failures/network-failure.ts
@@ -3,10 +3,10 @@ import BaseFailure from "./base-failure";
/**
* Failure for HTTP response when response dosn't have base structure
*/
-export default class NetworkFailure extends BaseFailure {
+export default class NetworkFailure extends BaseFailure {
/* ------------------------------- Constructor ------------------------------ */
- constructor() {
- super("network");
+ constructor(metaData?: META_DATA) {
+ super("network", metaData);
}
/* -------------------------------------------------------------------------- */
}
diff --git a/src/feature/common/failures/params-failure.ts b/src/feature/common/failures/params-failure.ts
new file mode 100644
index 0000000..df50d8c
--- /dev/null
+++ b/src/feature/common/failures/params-failure.ts
@@ -0,0 +1,12 @@
+import BaseFailure from "./base-failure";
+
+/**
+ * Failure for params failure
+ */
+export default class ParamsFailure extends BaseFailure {
+ /* ------------------------------- Constructor ------------------------------ */
+ constructor(metadata?: META_DATA) {
+ super("params", metadata);
+ }
+ /* -------------------------------------------------------------------------- */
+}
diff --git a/src/feature/core/customer-invoice/data/repo/customer-invoice-db-repo.ts b/src/feature/core/customer-invoice/data/repo/customer-invoice-db-repo.ts
index a12de93..bc35087 100644
--- a/src/feature/core/customer-invoice/data/repo/customer-invoice-db-repo.ts
+++ b/src/feature/core/customer-invoice/data/repo/customer-invoice-db-repo.ts
@@ -1,7 +1,12 @@
import { sql } from "@/bootstrap/boundaries/db/db";
+import ApiTask from "@/feature/common/data/api-task";
+import { failureOr } from "@/feature/common/failures/failure-helpers";
+import NetworkFailure from "@/feature/common/failures/network-failure";
import { formatCurrency } from "@/feature/common/feature-helpers";
import CustomerInvoice from "@/feature/core/customer-invoice/domain/entity/customer-invoice";
import CustomerInvoiceRepo from "@/feature/core/customer-invoice/domain/i-repo/customer-invoice-repo";
+import { pipe } from "fp-ts/lib/function";
+import { tryCatch } from "fp-ts/lib/TaskEither";
import postgres from "postgres";
type customerInvoiceDbResponse = {
@@ -13,23 +18,24 @@ type customerInvoiceDbResponse = {
}
export default class CustomerInvoiceDbRepo implements CustomerInvoiceRepo {
- async fetchList(): Promise {
- try {
- const data = await sql`
- SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
- FROM invoices
- JOIN customers ON invoices.customer_id = customers.id
- ORDER BY invoices.date DESC
- LIMIT 20 ` as postgres.RowList;
-
- return this.customerInvoicesDto(data)
- } catch (error) {
- console.error('Database Error:', error);
- throw new Error('Failed to fetch the latest invoices.');
- }
+ fetchList(): ApiTask {
+ return pipe(
+ tryCatch(
+ async () => {
+ const response = await sql`
+ SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
+ FROM invoices
+ JOIN customers ON invoices.customer_id = customers.id
+ ORDER BY invoices.date DESC
+ LIMIT 20 ` as postgres.RowList;
+
+ return this.customerInvoicesDto(response)
+ },
+ (l) => failureOr(l, new NetworkFailure())
+ )
+ )
}
-
private customerInvoicesDto(dbCustomers: customerInvoiceDbResponse[]): CustomerInvoice[] {
return dbCustomers.map((customer) => this.customerInvoiceDto(customer));
}
diff --git a/src/feature/core/customer-invoice/domain/i-repo/customer-invoice-repo.ts b/src/feature/core/customer-invoice/domain/i-repo/customer-invoice-repo.ts
index 0a8c993..ab4b486 100644
--- a/src/feature/core/customer-invoice/domain/i-repo/customer-invoice-repo.ts
+++ b/src/feature/core/customer-invoice/domain/i-repo/customer-invoice-repo.ts
@@ -1,7 +1,8 @@
+import ApiTask from "@/feature/common/data/api-task"
import CustomerInvoice from "@/feature/core/customer-invoice/domain/entity/customer-invoice"
export default interface CustomerInvoiceRepo {
- fetchList(): Promise
+ fetchList(): ApiTask
}
export const customerInvoiceRepoKey = "customerInvoiceRepoKey"
\ No newline at end of file
diff --git a/src/feature/core/customer-invoice/domain/usecase/fetch-customer-invoices-usecase.ts b/src/feature/core/customer-invoice/domain/usecase/fetch-customer-invoices-usecase.ts
index 5947553..5d2df55 100644
--- a/src/feature/core/customer-invoice/domain/usecase/fetch-customer-invoices-usecase.ts
+++ b/src/feature/core/customer-invoice/domain/usecase/fetch-customer-invoices-usecase.ts
@@ -1,12 +1,12 @@
-"use server"
+import { ApiEither } from "@/feature/common/data/api-task";
import serverDi from "@/feature/common/server-di";
import CustomerInvoice from "@/feature/core/customer-invoice/domain/entity/customer-invoice";
import CustomerInvoiceRepo, { customerInvoiceRepoKey } from "@/feature/core/customer-invoice/domain/i-repo/customer-invoice-repo";
import { customerInvoiceModuleKey } from "@/feature/core/customer-invoice/invoice-module-key";
import { connection } from "next/server";
-export default async function fetchCustomerInvoicesUsecase(): Promise {
+export default async function fetchCustomerInvoicesUsecase(): Promise> {
connection()
const repo = serverDi(customerInvoiceModuleKey).resolve(customerInvoiceRepoKey)
- return repo.fetchList()
+ return repo.fetchList()()
}
\ No newline at end of file
diff --git a/src/feature/core/invoice/data/repo/invoice-db-repo.ts b/src/feature/core/invoice/data/repo/invoice-db-repo.ts
index c8e2bb9..416c060 100644
--- a/src/feature/core/invoice/data/repo/invoice-db-repo.ts
+++ b/src/feature/core/invoice/data/repo/invoice-db-repo.ts
@@ -1,8 +1,13 @@
import { sql } from "@/bootstrap/boundaries/db/db";
+import ApiTask from "@/feature/common/data/api-task";
+import { failureOr } from "@/feature/common/failures/failure-helpers";
+import NetworkFailure from "@/feature/common/failures/network-failure";
import { formatCurrency } from "@/feature/common/feature-helpers";
import InvoiceRepo from "@/feature/core/invoice/domain/i-repo/invoice-repo";
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param";
import InvoiceStatusSummary from "@/feature/core/invoice/domain/value-object/invoice-status";
+import { pipe } from "fp-ts/lib/function";
+import { tryCatch } from "fp-ts/lib/TaskEither";
import postgres from "postgres";
type InvoiceSummaryDbResponse = {paid: string, pending: string}
@@ -13,26 +18,33 @@ export default class InvoiceDbRepo implements InvoiceRepo {
return data.count ?? 0
}
- async createInvoice(params: InvoiceParam): Promise {
- const firstCustomerIdDb = await sql`SELECT
- id FROM customers
- ORDER BY id DESC
- LIMIT 1
- `
- const customerId = firstCustomerIdDb.at(0)?.id
- if (!customerId) throw new Error("There is no customer")
-
- const { amount, status } = params;
- const amountInCents = amount * 100;
- const date = new Date().toISOString().split('T')[0];
-
- // Insert data into the database
- const result = await sql`
- INSERT INTO invoices (customer_id, amount, status, date)
- VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
- RETURNING id
- `;
- return result.at(0)?.id ?? ""
+ createInvoice(params: InvoiceParam): ApiTask {
+ return pipe(
+ tryCatch(
+ async () => {
+ const firstCustomerIdDb = await sql`SELECT
+ id FROM customers
+ ORDER BY id DESC
+ LIMIT 1
+ `
+ const customerId = firstCustomerIdDb.at(0)?.id
+ if (!customerId) throw new Error("There is no customer")
+
+ const { amount, status } = params;
+ const amountInCents = amount * 100;
+ const date = new Date().toISOString().split('T')[0];
+
+ // Insert data into the database
+ const result = await sql`
+ INSERT INTO invoices (customer_id, amount, status, date)
+ VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
+ RETURNING id
+ `;
+ return result.at(0)?.id ?? ""
+ },
+ (l) => failureOr(l, new NetworkFailure(l as Error))
+ ),
+ )
}
async fetchInvoicesStatusSummary(): Promise {
diff --git a/src/feature/core/invoice/domain/i-repo/invoice-repo.ts b/src/feature/core/invoice/domain/i-repo/invoice-repo.ts
index 983ac3f..ca5096d 100644
--- a/src/feature/core/invoice/domain/i-repo/invoice-repo.ts
+++ b/src/feature/core/invoice/domain/i-repo/invoice-repo.ts
@@ -1,10 +1,11 @@
+import ApiTask from "@/feature/common/data/api-task"
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param"
import InvoiceStatusSummary from "@/feature/core/invoice/domain/value-object/invoice-status"
export default interface InvoiceRepo {
fetchAllInvoicesAmount(): Promise
fetchInvoicesStatusSummary(): Promise
- createInvoice(params: InvoiceParam): Promise
+ createInvoice(params: InvoiceParam): ApiTask
}
export const invoiceRepoKey = "invoiceRepoKey"
\ No newline at end of file
diff --git a/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts b/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts
index 73a0106..9cfc63d 100644
--- a/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts
+++ b/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts
@@ -1,19 +1,24 @@
"use server"
+import { ApiEither } from "@/feature/common/data/api-task";
+import ParamsFailure from "@/feature/common/failures/params-failure";
import serverDi from "@/feature/common/server-di";
import InvoiceRepo, { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice-repo";
import { InvoiceParam, invoiceSchema } from "@/feature/core/invoice/domain/param/invoice-param";
import { invoiceModuleKey } from "@/feature/core/invoice/invoice-module-key";
+import { pipe } from "fp-ts/lib/function";
+import { chain, fromNullable, left, map, right } from "fp-ts/lib/TaskEither";
-export default async function createInvoiceUsecase(params: InvoiceParam): Promise {
- const isParamsValid = invoiceSchema.safeParse(params)
-
- if (!isParamsValid) {
- return {
- errorMessage: "Please pass correct params"
- }
- }
+export default async function createInvoiceUsecase(params: InvoiceParam): Promise> {
const repo = serverDi(invoiceModuleKey).resolve(invoiceRepoKey)
- return repo.createInvoice(params)
-
+ return pipe(
+ fromNullable(new ParamsFailure())(params),
+ map((params) => invoiceSchema.safeParse(params)),
+ chain((params) => {
+ const isParamsValid = invoiceSchema.safeParse(params)
+ if (!isParamsValid.success) left(new ParamsFailure())
+ return right(params.data as InvoiceParam)
+ }),
+ chain((params) => repo.createInvoice(params))
+ )()
}
\ No newline at end of file