develop #3
@ -1,5 +1,5 @@
|
|||||||
import fetchCustomerInvoicesUsecase from "@/feature/core/customer-invoice/domain/usecase/fetch-customer-invoices-usecase";
|
import fetchCustomerInvoicesUsecase from "@/feature/core/customer-invoice/domain/usecase/fetch-customer-invoices-usecase";
|
||||||
|
|
||||||
export default function latestInvoicesController() {
|
export default async function latestInvoicesController() {
|
||||||
return fetchCustomerInvoicesUsecase()
|
return await fetchCustomerInvoicesUsecase()
|
||||||
}
|
}
|
@ -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 latestInvoicesController from '@/app/dashboard/components/server/latest-invoices/latest-invoices-controller';
|
||||||
import { ArrowPathIcon } from '@heroicons/react/24/outline';
|
import { ArrowPathIcon } from '@heroicons/react/24/outline';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { isLeft } from 'fp-ts/lib/Either';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
export default async function LatestInvoices() {
|
export default async function LatestInvoices() {
|
||||||
const latestInvoices = await latestInvoicesController();
|
const latestInvoices = await latestInvoicesController();
|
||||||
|
|
||||||
const invoices = latestInvoices.map((invoice, i) => {
|
if (isLeft(latestInvoices)) return <div>Error</div>
|
||||||
|
|
||||||
|
const invoices = latestInvoices.right.map((invoice, i) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={invoice.id}
|
key={invoice.id}
|
||||||
|
@ -4,7 +4,7 @@ import { makeFailureMessage } from "@/feature/common/failures/failure-helpers";
|
|||||||
* This is a class called BaseFailure that extends the Error class. It is
|
* This is a class called BaseFailure that extends the Error class. It is
|
||||||
* used as a base class for creating custom failure classes.
|
* used as a base class for creating custom failure classes.
|
||||||
*/
|
*/
|
||||||
export default abstract class BaseFailure {
|
export default abstract class BaseFailure<META_DATA> {
|
||||||
/* ------------------------------- Attributes ------------------------------- */
|
/* ------------------------------- Attributes ------------------------------- */
|
||||||
private readonly BASE_FAILURE_MESSAGE = "failure";
|
private readonly BASE_FAILURE_MESSAGE = "failure";
|
||||||
|
|
||||||
@ -15,8 +15,12 @@ export default abstract class BaseFailure {
|
|||||||
message = this.BASE_FAILURE_MESSAGE;
|
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.message = makeFailureMessage(this.message, key);
|
||||||
|
this.metadata = metadata ?? undefined
|
||||||
}
|
}
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
@ -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
|
* Failure for needed arguments in a method but sent wrong one
|
||||||
*/
|
*/
|
||||||
export default class ArgumentsFailure extends BaseDevFailure {
|
export default class ArgumentsFailure<META_DATA> extends BaseDevFailure<META_DATA> {
|
||||||
/* ------------------------------- Constructor ------------------------------ */
|
/* ------------------------------- Constructor ------------------------------ */
|
||||||
constructor() {
|
constructor(metadata?: META_DATA) {
|
||||||
super("arguments");
|
super("arguments", metadata);
|
||||||
}
|
}
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
import BaseFailure from "@/feature/common/failures/base-failure";
|
import BaseFailure from "@/feature/common/failures/base-failure";
|
||||||
|
|
||||||
export default abstract class BaseDevFailure extends BaseFailure {}
|
export default abstract class BaseDevFailure<META_DATA> extends BaseFailure<META_DATA> {}
|
||||||
|
@ -3,8 +3,8 @@ import BaseDevFailure from "@/feature/common/failures/dev/base-dev-failure";
|
|||||||
/**
|
/**
|
||||||
* This is a failure of not having specific dependency
|
* This is a failure of not having specific dependency
|
||||||
*/
|
*/
|
||||||
export default class DependencyFailure extends BaseDevFailure {
|
export default class DependencyFailure<META_DATA> extends BaseDevFailure<META_DATA> {
|
||||||
constructor() {
|
constructor(metadata: META_DATA) {
|
||||||
super("DependencyFailure");
|
super("DependencyFailure", metadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,10 @@ import BaseFailure from "./base-failure";
|
|||||||
/**
|
/**
|
||||||
* Failure for HTTP response when response dosn't have base structure
|
* Failure for HTTP response when response dosn't have base structure
|
||||||
*/
|
*/
|
||||||
export default class NetworkFailure extends BaseFailure {
|
export default class NetworkFailure<META_DATA> extends BaseFailure<META_DATA> {
|
||||||
/* ------------------------------- Constructor ------------------------------ */
|
/* ------------------------------- Constructor ------------------------------ */
|
||||||
constructor() {
|
constructor(metaData?: META_DATA) {
|
||||||
super("network");
|
super("network", metaData);
|
||||||
}
|
}
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
12
src/feature/common/failures/params-failure.ts
Normal file
12
src/feature/common/failures/params-failure.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import BaseFailure from "./base-failure";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Failure for params failure
|
||||||
|
*/
|
||||||
|
export default class ParamsFailure<META_DATA> extends BaseFailure<META_DATA> {
|
||||||
|
/* ------------------------------- Constructor ------------------------------ */
|
||||||
|
constructor(metadata?: META_DATA) {
|
||||||
|
super("params", metadata);
|
||||||
|
}
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
}
|
@ -1,7 +1,12 @@
|
|||||||
import { sql } from "@/bootstrap/boundaries/db/db";
|
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 { formatCurrency } from "@/feature/common/feature-helpers";
|
||||||
import CustomerInvoice from "@/feature/core/customer-invoice/domain/entity/customer-invoice";
|
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 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";
|
import postgres from "postgres";
|
||||||
|
|
||||||
type customerInvoiceDbResponse = {
|
type customerInvoiceDbResponse = {
|
||||||
@ -13,22 +18,23 @@ type customerInvoiceDbResponse = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class CustomerInvoiceDbRepo implements CustomerInvoiceRepo {
|
export default class CustomerInvoiceDbRepo implements CustomerInvoiceRepo {
|
||||||
async fetchList(): Promise<CustomerInvoice[]> {
|
fetchList(): ApiTask<CustomerInvoice[]> {
|
||||||
try {
|
return pipe(
|
||||||
const data = await sql`
|
tryCatch(
|
||||||
|
async () => {
|
||||||
|
const response = await sql`
|
||||||
SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
|
SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
|
||||||
FROM invoices
|
FROM invoices
|
||||||
JOIN customers ON invoices.customer_id = customers.id
|
JOIN customers ON invoices.customer_id = customers.id
|
||||||
ORDER BY invoices.date DESC
|
ORDER BY invoices.date DESC
|
||||||
LIMIT 20 ` as postgres.RowList<customerInvoiceDbResponse[]>;
|
LIMIT 20 ` as postgres.RowList<customerInvoiceDbResponse[]>;
|
||||||
|
|
||||||
return this.customerInvoicesDto(data)
|
return this.customerInvoicesDto(response)
|
||||||
} catch (error) {
|
},
|
||||||
console.error('Database Error:', error);
|
(l) => failureOr(l, new NetworkFailure())
|
||||||
throw new Error('Failed to fetch the latest invoices.');
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private customerInvoicesDto(dbCustomers: customerInvoiceDbResponse[]): CustomerInvoice[] {
|
private customerInvoicesDto(dbCustomers: customerInvoiceDbResponse[]): CustomerInvoice[] {
|
||||||
return dbCustomers.map((customer) => this.customerInvoiceDto(customer));
|
return dbCustomers.map((customer) => this.customerInvoiceDto(customer));
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import ApiTask from "@/feature/common/data/api-task"
|
||||||
import CustomerInvoice from "@/feature/core/customer-invoice/domain/entity/customer-invoice"
|
import CustomerInvoice from "@/feature/core/customer-invoice/domain/entity/customer-invoice"
|
||||||
|
|
||||||
export default interface CustomerInvoiceRepo {
|
export default interface CustomerInvoiceRepo {
|
||||||
fetchList(): Promise<CustomerInvoice[]>
|
fetchList(): ApiTask<CustomerInvoice[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const customerInvoiceRepoKey = "customerInvoiceRepoKey"
|
export const customerInvoiceRepoKey = "customerInvoiceRepoKey"
|
@ -1,12 +1,12 @@
|
|||||||
"use server"
|
import { ApiEither } from "@/feature/common/data/api-task";
|
||||||
import serverDi from "@/feature/common/server-di";
|
import serverDi from "@/feature/common/server-di";
|
||||||
import CustomerInvoice from "@/feature/core/customer-invoice/domain/entity/customer-invoice";
|
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 CustomerInvoiceRepo, { customerInvoiceRepoKey } from "@/feature/core/customer-invoice/domain/i-repo/customer-invoice-repo";
|
||||||
import { customerInvoiceModuleKey } from "@/feature/core/customer-invoice/invoice-module-key";
|
import { customerInvoiceModuleKey } from "@/feature/core/customer-invoice/invoice-module-key";
|
||||||
import { connection } from "next/server";
|
import { connection } from "next/server";
|
||||||
|
|
||||||
export default async function fetchCustomerInvoicesUsecase(): Promise<CustomerInvoice[]> {
|
export default async function fetchCustomerInvoicesUsecase(): Promise<ApiEither<CustomerInvoice[]>> {
|
||||||
connection()
|
connection()
|
||||||
const repo = serverDi(customerInvoiceModuleKey).resolve<CustomerInvoiceRepo>(customerInvoiceRepoKey)
|
const repo = serverDi(customerInvoiceModuleKey).resolve<CustomerInvoiceRepo>(customerInvoiceRepoKey)
|
||||||
return repo.fetchList()
|
return repo.fetchList()()
|
||||||
}
|
}
|
@ -1,8 +1,13 @@
|
|||||||
import { sql } from "@/bootstrap/boundaries/db/db";
|
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 { formatCurrency } from "@/feature/common/feature-helpers";
|
||||||
import InvoiceRepo from "@/feature/core/invoice/domain/i-repo/invoice-repo";
|
import InvoiceRepo from "@/feature/core/invoice/domain/i-repo/invoice-repo";
|
||||||
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param";
|
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param";
|
||||||
import InvoiceStatusSummary from "@/feature/core/invoice/domain/value-object/invoice-status";
|
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";
|
import postgres from "postgres";
|
||||||
|
|
||||||
type InvoiceSummaryDbResponse = {paid: string, pending: string}
|
type InvoiceSummaryDbResponse = {paid: string, pending: string}
|
||||||
@ -13,7 +18,10 @@ export default class InvoiceDbRepo implements InvoiceRepo {
|
|||||||
return data.count ?? 0
|
return data.count ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
async createInvoice(params: InvoiceParam): Promise<string> {
|
createInvoice(params: InvoiceParam): ApiTask<string> {
|
||||||
|
return pipe(
|
||||||
|
tryCatch(
|
||||||
|
async () => {
|
||||||
const firstCustomerIdDb = await sql`SELECT
|
const firstCustomerIdDb = await sql`SELECT
|
||||||
id FROM customers
|
id FROM customers
|
||||||
ORDER BY id DESC
|
ORDER BY id DESC
|
||||||
@ -33,6 +41,10 @@ export default class InvoiceDbRepo implements InvoiceRepo {
|
|||||||
RETURNING id
|
RETURNING id
|
||||||
`;
|
`;
|
||||||
return result.at(0)?.id ?? ""
|
return result.at(0)?.id ?? ""
|
||||||
|
},
|
||||||
|
(l) => failureOr(l, new NetworkFailure(l as Error))
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchInvoicesStatusSummary(): Promise<InvoiceStatusSummary> {
|
async fetchInvoicesStatusSummary(): Promise<InvoiceStatusSummary> {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
import ApiTask from "@/feature/common/data/api-task"
|
||||||
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param"
|
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param"
|
||||||
import InvoiceStatusSummary from "@/feature/core/invoice/domain/value-object/invoice-status"
|
import InvoiceStatusSummary from "@/feature/core/invoice/domain/value-object/invoice-status"
|
||||||
|
|
||||||
export default interface InvoiceRepo {
|
export default interface InvoiceRepo {
|
||||||
fetchAllInvoicesAmount(): Promise<number>
|
fetchAllInvoicesAmount(): Promise<number>
|
||||||
fetchInvoicesStatusSummary(): Promise<InvoiceStatusSummary>
|
fetchInvoicesStatusSummary(): Promise<InvoiceStatusSummary>
|
||||||
createInvoice(params: InvoiceParam): Promise<string>
|
createInvoice(params: InvoiceParam): ApiTask<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const invoiceRepoKey = "invoiceRepoKey"
|
export const invoiceRepoKey = "invoiceRepoKey"
|
@ -1,19 +1,24 @@
|
|||||||
"use server"
|
"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 serverDi from "@/feature/common/server-di";
|
||||||
import InvoiceRepo, { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice-repo";
|
import InvoiceRepo, { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice-repo";
|
||||||
import { InvoiceParam, invoiceSchema } from "@/feature/core/invoice/domain/param/invoice-param";
|
import { InvoiceParam, invoiceSchema } from "@/feature/core/invoice/domain/param/invoice-param";
|
||||||
import { invoiceModuleKey } from "@/feature/core/invoice/invoice-module-key";
|
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<string | {errorMessage: string}> {
|
export default async function createInvoiceUsecase(params: InvoiceParam): Promise<ApiEither<string>> {
|
||||||
const isParamsValid = invoiceSchema.safeParse(params)
|
|
||||||
|
|
||||||
if (!isParamsValid) {
|
|
||||||
return {
|
|
||||||
errorMessage: "Please pass correct params"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const repo = serverDi(invoiceModuleKey).resolve<InvoiceRepo>(invoiceRepoKey)
|
const repo = serverDi(invoiceModuleKey).resolve<InvoiceRepo>(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))
|
||||||
|
)()
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user