feature/research-di #1

Merged
behnam merged 37 commits from feature/research-di into develop 2024-11-21 15:50:19 +00:00
10 changed files with 108 additions and 8 deletions
Showing only changes of commit 021511e60e - Show all commits

View File

@ -15,6 +15,6 @@
"utils": "@/bootstrap/helpers/lib/ui-utils", "utils": "@/bootstrap/helpers/lib/ui-utils",
"ui": "@/app/components", "ui": "@/app/components",
"lib": "@/bootstrap/helpers/lib", "lib": "@/bootstrap/helpers/lib",
"hooks": "@/bootstrap/helpers/vm/hooks" "hooks": "@/bootstrap/helpers/hooks"
} }
} }

View File

@ -48,9 +48,9 @@ export default async function LatestInvoices() {
<h2 className="mb-4 text-xl md:text-2xl"> <h2 className="mb-4 text-xl md:text-2xl">
Latest Invoices Latest Invoices
</h2> </h2>
<div className="flex grow flex-col justify-between rounded-xl bg-gray-50 p-4"> <div className="flex grow flex-col max-h-[66.5vh] justify-between rounded-xl bg-gray-50 p-4">
<div className="bg-white px-6"> <div className="bg-white px-6 h-full overflow-y-auto">
{invoices} {invoices}
</div> </div>
<div className="flex items-end mt-auto pb-2 pt-6"> <div className="flex items-end mt-auto pb-2 pt-6">

View File

@ -1,9 +1,13 @@
import CreateRandomInvoiceButtonVM from "@/app/dashboard/vm/create-random-invoice-button-vm"; import CreateRandomInvoiceButtonVM from "@/app/dashboard/vm/create-random-invoice-button-vm";
import di from "@/bootstrap/di/init-di" import di from "@/bootstrap/di/init-di"
import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice-usecase";
export default function dashboardAppModule() { export default function dashboardAppModule() {
const dashboardDi = di.createChildContainer() const dashboardDi = di.createChildContainer()
dashboardDi.register(createInvoiceUsecase.name, {
useValue: createInvoiceUsecase
})
dashboardDi.register(CreateRandomInvoiceButtonVM, CreateRandomInvoiceButtonVM) dashboardDi.register(CreateRandomInvoiceButtonVM, CreateRandomInvoiceButtonVM)
return dashboardDi return dashboardDi
} }

View File

@ -1,15 +1,36 @@
import ButtonVm from "@/app/components/button/button-vm"; import ButtonVm from "@/app/components/button/button-vm";
import useThrottle from "@/bootstrap/helpers/hooks/use-throttle";
import BaseVM from "@/bootstrap/helpers/vm/base-vm"; import BaseVM from "@/bootstrap/helpers/vm/base-vm";
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param";
import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice-usecase";
import { faker } from "@faker-js/faker";
export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> { export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
private createInvoice: typeof createInvoiceUsecase
constructor() {
super()
this.createInvoice = this.di.resolve(createInvoiceUsecase.name)
}
useVM(): ButtonVm { useVM(): ButtonVm {
const throttledOnClick = useThrottle(this.onClickHandler.bind(this), 5000)
return { return {
props: { props: {
title: "Button Title" title: "Create Random Invoice"
}, },
onClick: () => { onClick: throttledOnClick
console.log('clicked');
} }
} }
onClickHandler() {
const fakedParams: InvoiceParam = {
amount: faker.number.int({
min: 1,
max: 10
}),
status: "paid"
}
this.createInvoice(fakedParams)
} }
} }

View File

@ -0,0 +1,18 @@
"use client"
import { useEffect, useRef } from "react"
/**
*
* @param callback
* @param time In miliseconds
*/
export default function useThrottle<T extends Function>(callback: T, time: number = 2000) {
const lastRun = useRef(Date.now())
return function() {
if (Date.now() - lastRun.current <= time) return;
lastRun.current = Date.now()
return callback()
}
}

View File

@ -20,7 +20,7 @@ export default class CustomerInvoiceDbRepo implements CustomerInvoiceRepo {
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 5` as postgres.RowList<customerInvoiceDbResponse[]>; LIMIT 20 ` as postgres.RowList<customerInvoiceDbResponse[]>;
return this.customerInvoicesDto(data) return this.customerInvoicesDto(data)
} catch (error) { } catch (error) {

View File

@ -1,6 +1,7 @@
import { sql } from "@/bootstrap/db/db"; import { sql } from "@/bootstrap/db/db";
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 InvoiceStatusSummary from "@/feature/core/invoice/domain/value-object/invoice-status"; import InvoiceStatusSummary from "@/feature/core/invoice/domain/value-object/invoice-status";
import postgres from "postgres"; import postgres from "postgres";
@ -12,6 +13,28 @@ export default class InvoiceDbRepo implements InvoiceRepo {
return data.count ?? 0 return data.count ?? 0
} }
async createInvoice(params: InvoiceParam): Promise<string> {
const firstCustomerIdDb = await sql`SELECT
id FROM customers
ORDER BY id ASC
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 ?? ""
}
async fetchInvoicesStatusSummary(): Promise<InvoiceStatusSummary> { async fetchInvoicesStatusSummary(): Promise<InvoiceStatusSummary> {
const invoiceStatusPromise = await sql`SELECT const invoiceStatusPromise = await sql`SELECT
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid", SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid",

View File

@ -1,8 +1,10 @@
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>
} }
export const invoiceRepoKey = "invoiceRepoKey" export const invoiceRepoKey = "invoiceRepoKey"

View File

@ -0,0 +1,10 @@
import { z } from "zod";
export const invoiceSchema = z.object({
amount: z.coerce.number().gt(0, { message: 'Please enter an amount greater than $0.' }),
status: z.enum(['pending', 'paid'], {
invalid_type_error: 'Please select an invoice status.',
}),
});
export type InvoiceParam = z.infer<typeof invoiceSchema>

View File

@ -0,0 +1,22 @@
"use server"
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 { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export default async function createInvoiceUsecase(params: InvoiceParam): Promise<string | {errorMessage: string}> {
const isParamsValid = invoiceSchema.safeParse(params)
if (!isParamsValid) {
return {
errorMessage: "Please pass correct params"
}
}
const repo = serverDi(invoiceModuleKey).resolve<InvoiceRepo>(invoiceRepoKey)
revalidatePath("/dashboard")
return repo.createInvoice(params)
}