Skip to content

Commit 37b6f03

Browse files
committed
feat: added cron job, db queries, formatting, db logic, optimizations, type refactors...
1 parent 79c5e2e commit 37b6f03

20 files changed

+1020
-127
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
"lint": "next lint"
1111
},
1212
"dependencies": {
13+
"@supabase/supabase-js": "^2.39.7",
1314
"@tanstack/react-table": "^8.12.0",
1415
"axios": "^1.6.7",
16+
"bitcoin-core": "^4.1.0",
1517
"clsx": "^2.0.0",
1618
"mobula-sdk": "^1.5.4",
1719
"next": "13.5.6",

src/components/common/PriceTable/DynamicTable.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@ import {
77
import styles from "./styles.module.scss";
88

99
interface TableProps {
10-
data: PerformanceDataType[];
11-
columns: ColumnDef<PerformanceDataType, any>[];
1210
title: string;
11+
data: PerformanceDataType[] | MarketStatsDataType[];
12+
columns: ColumnDef<any, any>[];
13+
className?: string;
1314
}
1415

15-
const DynamicTable = ({ data, columns, title }: TableProps) => {
16+
const DynamicTable = ({ data, columns, title, className }: TableProps) => {
1617
const table = useReactTable({
1718
data,
1819
columns: columns,
1920
getCoreRowModel: getCoreRowModel(),
2021
});
2122

2223
return (
23-
<div className={styles.dynamicTable}>
24+
<div className={`${styles.dynamicTable} ${className}`}>
2425
<p>{title}</p>
2526
<table>
2627
<thead>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { createColumnHelper } from "@tanstack/react-table";
2+
import styles from "./styles.module.scss";
3+
4+
const columnHelperMarket = createColumnHelper<FormattedMarketStats>();
5+
const columnHelperPerformance = createColumnHelper<PerformanceDataType>();
6+
7+
export const performanceColumns = [
8+
columnHelperPerformance.accessor("period", {
9+
id: "period",
10+
cell: (info) => info.getValue(),
11+
header: () => <span>Period</span>,
12+
}),
13+
columnHelperPerformance.accessor((row) => row.priceChange, {
14+
id: "priceChange",
15+
cell: (info) => (
16+
<span
17+
className={info.getValue().includes("-") ? styles.red : styles.green}
18+
>
19+
{info.getValue()}
20+
</span>
21+
),
22+
header: () => <span>Change</span>,
23+
}),
24+
columnHelperPerformance.accessor("percentageChange", {
25+
id: "percentageChange",
26+
cell: (info) => (
27+
<span
28+
className={info.getValue().includes("-") ? styles.red : styles.green}
29+
>
30+
{info.getValue()}
31+
</span>
32+
),
33+
header: () => <span>Change(%)</span>,
34+
}),
35+
columnHelperPerformance.accessor("highestPrice", {
36+
id: "highestPrice",
37+
cell: (info) => <span>{info.getValue()}</span>,
38+
header: () => <span>High</span>,
39+
}),
40+
];
41+
42+
export const marketStatsColumn = [
43+
columnHelperMarket.accessor("label" as any, {
44+
id: "label",
45+
cell: (info) => <span>{info.getValue()}</span>,
46+
header: () => <span></span>,
47+
}),
48+
columnHelperMarket.accessor((row) => row.latest, {
49+
id: "latest",
50+
cell: (info) => <span>{formatNumber(Number(info.getValue()))}</span>,
51+
header: () => <span>Latest</span>,
52+
}),
53+
columnHelperMarket.accessor("changeInNumber", {
54+
id: "changeInNumber",
55+
cell: (info) => (
56+
<span
57+
className={
58+
String(info.getValue()).includes("-") ? styles.red : styles.green
59+
}
60+
>
61+
{info.getValue() === undefined
62+
? "-"
63+
: formatNumber(Number(info.getValue()))}
64+
</span>
65+
),
66+
header: () => <span>1w Change</span>,
67+
}),
68+
columnHelperMarket.accessor("changeInPercent", {
69+
id: "changeInPercent",
70+
cell: (info) => (
71+
<span
72+
className={
73+
String(info.getValue()).includes("-") ? styles.red : styles.green
74+
}
75+
>
76+
{info.getValue() === undefined ? "-" : `${info.getValue()}%`}
77+
</span>
78+
),
79+
header: () => <span>1w Change(%)</span>,
80+
}),
81+
];
82+
83+
const formatNumber = (number: number): string => {
84+
if (number === 0) return "0";
85+
86+
const isNegative = number < 0;
87+
const absNumber = Math.abs(number);
88+
const suffixes = ["", "thousand", "million", "billion", "trillion"];
89+
const magnitude = Math.floor(Math.log10(absNumber) / 3);
90+
const scaledNumber = absNumber / Math.pow(10, magnitude * 3);
91+
const roundedNumber = Math.round(scaledNumber * 10) / 10; // Round to one decimal place
92+
93+
return `${isNegative ? "-" : ""}${roundedNumber} ${suffixes[magnitude]}`;
94+
};
+20-64
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,30 @@
1-
import {
2-
flexRender,
3-
useReactTable,
4-
getCoreRowModel,
5-
createColumnHelper,
6-
TableState,
7-
} from "@tanstack/react-table";
8-
import styles from "./styles.module.scss";
91
import DynamicTable from "./DynamicTable";
2+
import { marketStatsColumn, performanceColumns } from "./coulms";
3+
import styles from "./styles.module.scss";
104

115
interface PriceTableProps {
126
title: string;
137
performance: PerformanceDataType[];
14-
marketStats?: any;
8+
marketStats: MarketStatsDataType[];
159
}
1610

17-
const columnHelper = createColumnHelper<PerformanceDataType>();
18-
19-
const performanceColumns = [
20-
columnHelper.accessor("period", {
21-
id: "period",
22-
cell: (info) => info.getValue(),
23-
header: () => <span>Period</span>,
24-
}),
25-
columnHelper.accessor((row) => row.priceChange, {
26-
id: "priceChange",
27-
cell: (info) => (
28-
<span
29-
className={info.getValue().includes("-") ? styles.red : styles.green}
30-
>
31-
{info.getValue()}
32-
</span>
33-
),
34-
header: () => <span>Change</span>,
35-
}),
36-
columnHelper.accessor("percentageChange", {
37-
id: "percentageChange",
38-
cell: (info) => (
39-
<span
40-
className={info.getValue().includes("-") ? styles.red : styles.green}
41-
>
42-
{info.getValue()}
43-
</span>
44-
),
45-
header: () => <span>Change(%)</span>,
46-
}),
47-
columnHelper.accessor("highestPrice", {
48-
id: "highestPrice",
49-
cell: (info) => <span>{info.getValue()}</span>,
50-
header: () => <span>High</span>,
51-
}),
52-
];
53-
54-
const PriceTable = ({ title, performance }: PriceTableProps) => {
55-
return (
56-
<div className={styles.priceTable}>
57-
<h1>{title}</h1>
58-
<div className={styles.tableGrid}>
59-
<DynamicTable
60-
title="Bitcoin Price Performance"
61-
data={performance}
62-
columns={performanceColumns}
63-
/>
64-
<DynamicTable
65-
title="Bitcoin Market Stats"
66-
data={performance}
67-
columns={performanceColumns}
68-
/>
69-
</div>
11+
const PriceTable = ({ title, performance, marketStats }: PriceTableProps) => (
12+
<div className={styles.priceTable}>
13+
<h1>{title}</h1>
14+
<div className={styles.tableGrid}>
15+
<DynamicTable
16+
title="Bitcoin Price Performance"
17+
data={performance}
18+
columns={performanceColumns}
19+
/>
20+
<DynamicTable
21+
title="Bitcoin Market Stats"
22+
data={marketStats}
23+
className={styles.marketTable}
24+
columns={marketStatsColumn}
25+
/>
7026
</div>
71-
);
72-
};
27+
</div>
28+
);
7329

7430
export default PriceTable;

src/components/common/PriceTable/styles.module.scss

+8-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
gap: 30px;
77

88
h1 {
9-
font-size: 44px;
9+
font-size: 36px;
1010
text-transform: uppercase;
1111
color: #252525;
1212

@@ -17,12 +17,16 @@
1717
}
1818

1919
.tableGrid {
20+
width: 100%;
2021
display: flex;
2122
gap: 40px;
2223
}
2324

25+
.marketTable {
26+
}
27+
2428
.dynamicTable {
25-
width: 100%;
29+
width: 140%;
2630

2731
p {
2832
font-weight: 600;
@@ -31,18 +35,12 @@
3135
}
3236

3337
table {
34-
min-width: 500px;
38+
width: 100%;
3539
text-align: left;
3640

3741
thead {
3842
height: 60px;
3943

40-
th {
41-
&:first-child {
42-
width: 20%;
43-
}
44-
}
45-
4644
tr {
4745
border-top: 1px solid #252525;
4846
}
@@ -57,6 +55,7 @@
5755
}
5856

5957
td {
58+
width: fit-content;
6059
padding: 8px;
6160
}
6261
}

src/pages/api/[db-job].ts

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type { NextApiRequest, NextApiResponse } from "next";
2+
import {
3+
assetTypeMapping,
4+
getPricePerformance,
5+
getMarketPerformance,
6+
} from "@/utils/api";
7+
import {
8+
getMarketStats,
9+
insertMarketStats,
10+
getPerformanceStats,
11+
deleteOldMarketStats,
12+
insertPerformanceStats,
13+
deleteOldPerfomanceStats,
14+
} from "@/utils/database";
15+
16+
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
17+
try {
18+
const { query } = req;
19+
const type = query.type;
20+
21+
const performance = await getPricePerformance(
22+
type as keyof typeof assetTypeMapping
23+
);
24+
const marketStats = await getMarketPerformance(
25+
type as keyof typeof assetTypeMapping
26+
);
27+
28+
if (performance) {
29+
for (let i = 0; i < performance.length; i++) {
30+
await insertPerformanceStats({
31+
...performance[i],
32+
type: query.type as string,
33+
});
34+
console.log(`Updating period: ${performance[i].period}`);
35+
}
36+
console.log(`Updated performance stats for type: ${query.type}`);
37+
38+
const performanceDB = await getPerformanceStats(
39+
type as keyof typeof assetTypeMapping
40+
);
41+
42+
if (performanceDB.data) {
43+
const firstSevenRecords = performanceDB.data
44+
.slice(0, 7)
45+
.map((rec) => rec.id);
46+
47+
await deleteOldPerfomanceStats(firstSevenRecords);
48+
49+
console.log(`Deleted oldest market stats record for ${query.type}`);
50+
}
51+
}
52+
53+
if (marketStats) {
54+
await insertMarketStats(marketStats);
55+
console.log(`Updated market stats for ${query.type}`);
56+
57+
const marketStatsDB = await getMarketStats(
58+
type as keyof typeof assetTypeMapping
59+
);
60+
61+
if (marketStatsDB.data && marketStatsDB.data.length > 7) {
62+
const idToDelete = marketStatsDB.data[0].id;
63+
await deleteOldMarketStats(idToDelete);
64+
console.log(`Deleted oldest market stats record for ${query.type}`);
65+
}
66+
}
67+
68+
res.status(200).json({ message: "Succesfully updated db" });
69+
} catch (err: any) {
70+
res.status(500).json({ message: err.message });
71+
}
72+
};
73+
74+
export default handler;

0 commit comments

Comments
 (0)