Build a Finance SaaS Platform -16 (Change to Local PG database)

Background

Neon is too slow and sometime can not reach, I decided to change to Local database

BTW, I tried to switch to Mysql but failed, too differences with PG. So, I still use PG.

Don’t use edge

Modify app/api/[[…route]]/route.ts:

1
2
3
...
// export const runtime = 'edge'
...

Change Connenct String

Modify db/drizzle.ts

1
2
3
4
5
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

export const sql = postgres(process.env.DATABASE_URL!);
export const db = drizzle(sql);

Modify scripts/migrate.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

import { config } from "dotenv";
import { drizzle } from 'drizzle-orm/postgres-js';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import postgres from 'postgres';

config({ path:".env.local"});

const sql = postgres(process.env.DATABASE_URL!);
const db = drizzle(sql);

const main = async () => {""
try {
await migrate(db, { migrationsFolder: "drizzle" });
} catch (error) {
console.error("Error during migration:", error );
process.exit();
}
};

main();

Build a Finance SaaS Platform -12(Edit Sheet)

Add get id API(Server)

Modify app/api/[[...route]]/accounts.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
...
.get(
"/:id",
zValidator("param",z.object({
id: z.string().optional(),
})),
clerkMiddleware(),
async (c) => {
const auth = getAuth(c);
const { id } = c.req.valid("param");

if (!id) {
return c.json({ error: "Missing id!" }, 400);
}

if (!auth?.userId) {
return c.json({ error: "Unauthorized!" }, 401);
}

const [data] = await db
.select({
id: accounts.id,
name: accounts.name,
})
.from(accounts)
.where(
and(
eq(accounts.userId, auth.userId),
eq(accounts.id, id)
),
);

if (!data) {
return c.json({ error: "Not Found!"}, 404);
};

return c.json({ data });
}
)
...
More...

Build a Finance SaaS Platform -11 (Delete API)

Modify Columns

Modify app/(dashboard)/accounts/columns.tsx:

(delete demo columns)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
"use client"
import { InferResponseType } from "hono";
import { ColumnDef } from "@tanstack/react-table";
import { ArrowUpDown } from "lucide-react";

import { client } from "@/lib/hono";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";

export type ResponsType = InferResponseType<typeof client.api.accounts.$get, 200>["data"][0];

export const columns: ColumnDef<ResponsType>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "name",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Name
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
]

More...

Build a Finance SaaS Platform -10 (Data Table Action)

Add Pagenation

Modify components/data-table.tsx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
...
import {
...
getPaginationRowModel,
...
} from "@tanstack/react-table"
...
...
const table = useReactTable({
...
getPaginationRowModel: getPaginationRowModel(),
...
})
...
return (
<div>
<div className="rounded-md border">
...
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
)
...
More...

Build a Finance SaaS Platform -9 (Accounts Page)

Install Card

1
npx shadcn@latest add card

Add Accounts Page

Add app/(dashboard)/accounts/page.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
"use client";

import { Plus } from "lucide-react";
import { useNewAccount } from "@/features/accounts/hooks/use-new-account";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";

import { Payment, columns } from "./columns";
import { DataTable } from "@/components/data-table";

const data: Payment[] = [
{
id: "728ed52f",
amount: 100,
status: "pending",
email: "m@example.com",
},
];

const AccountsPage = ()=> {
const newAccount = useNewAccount();

return (
<div className="max-w-screen-2xl mx-auto w-full pb-10 -mt-24">
<Card className="border-none drop-shadow-sm">
<CardHeader className="gap-y-2 lg:flex-row lg:items-center lg:justify-between">
<CardTitle className="text-xl line-clamp-1">
Account Page
</CardTitle>
<Button onClick={newAccount.onOpen} size="sm">
<Plus className="size-4 mr-2" />
Add New
</Button>
</CardHeader>
<CardContent>
<DataTable columns={columns} data={data} />
</CardContent>
</Card>
</div>
);
};

export default AccountsPage;
More...

Build a Finance SaaS Platform -7 (Post Method)

Install drizzle-zod

1
2
npm i drizzle-zod
npm i @hono/zod-validator

Install cuid2

1
npm install --save @paralleldrive/cuid2

Add Post Method

Modify app/api/[[...route]]/accounts.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { createId } from "@paralleldrive/cuid2";
import { clerkMiddleware, getAuth } from "@hono/clerk-auth";

import {db} from "@/db/drizzle";
import {accounts, insertAccountSchema} from "@/db/schema";
import { eq } from "drizzle-orm";

const app = new Hono()
.get("/", clerkMiddleware(), async (c) => {
const auth = getAuth(c);

if( !auth?.userId ) {
return c.json({ error:"Unauthorized!"}, 401);
}

const data = await db.select({
id: accounts.id,
name: accounts.name,
}).from(accounts)
.where(eq(accounts.userId, auth.userId));

return c.json({ accounts: data });
})
.post(
"/",
clerkMiddleware(),
zValidator('json',insertAccountSchema.pick({
name:true,
})),
async(c) => {
const auth = getAuth(c);
const values = c.req.valid("json");

if(!auth?.userId){
return c.json({ error:"Unauthorized!"}, 401);
}

const data = await db.insert(accounts).values({
id: createId(),
userId: auth.userId,
...values,
}).returning();

return c.json({ data });
})

export default app;

请我喝杯咖啡吧~

支付宝
微信