diff --git a/dashboard/15-final/.env b/dashboard/15-final/.env new file mode 100644 index 0000000..7241962 --- /dev/null +++ b/dashboard/15-final/.env @@ -0,0 +1,7 @@ +POSTGRES_URL="postgres://default:IQA2FonHJi9d@ep-square-union-29037633-pooler.us-east-1.postgres.vercel-storage.com:5432/verceldb" +POSTGRES_PRISMA_URL="postgres://default:IQA2FonHJi9d@ep-square-union-29037633-pooler.us-east-1.postgres.vercel-storage.com:5432/verceldb?pgbouncer=true&connect_timeout=15" +POSTGRES_URL_NON_POOLING="postgres://default:IQA2FonHJi9d@ep-square-union-29037633.us-east-1.postgres.vercel-storage.com:5432/verceldb" +POSTGRES_USER="default" +POSTGRES_HOST="ep-square-union-29037633-pooler.us-east-1.postgres.vercel-storage.com" +POSTGRES_PASSWORD="IQA2FonHJi9d" +POSTGRES_DATABASE="verceldb" \ No newline at end of file diff --git a/dashboard/15-final/app/dashboard/invoices/[id]/edit/page.tsx b/dashboard/15-final/app/dashboard/invoices/[id]/edit/page.tsx index 0939c16..6aca81f 100644 --- a/dashboard/15-final/app/dashboard/invoices/[id]/edit/page.tsx +++ b/dashboard/15-final/app/dashboard/invoices/[id]/edit/page.tsx @@ -1,10 +1,12 @@ import InvoiceForm from '@/app/ui/invoices/form'; -import { invoices } from '@/app/lib/dummy-data'; import { notFound } from 'next/navigation'; +import { Invoice } from '@/app/lib/definitions'; +import { fetchInvoiceById } from '@/app/lib/data-fetches'; -export default function Page({ params }: { params: { id: string } }) { +export default async function Page({ params }: { params: { id: string } }) { const id = params.id ? parseInt(params.id) : null; - const invoice = invoices.find((invoice) => invoice.id === id); + const invoiceData = await fetchInvoiceById(id); + const invoice = invoiceData.rows[0] as Invoice; if (!invoice) { notFound(); diff --git a/dashboard/15-final/app/lib/actions.tsx b/dashboard/15-final/app/lib/actions.ts similarity index 100% rename from dashboard/15-final/app/lib/actions.tsx rename to dashboard/15-final/app/lib/actions.ts diff --git a/dashboard/15-final/app/lib/calculations.tsx b/dashboard/15-final/app/lib/calculations.ts similarity index 82% rename from dashboard/15-final/app/lib/calculations.tsx rename to dashboard/15-final/app/lib/calculations.ts index 7f90579..2dd143a 100644 --- a/dashboard/15-final/app/lib/calculations.tsx +++ b/dashboard/15-final/app/lib/calculations.ts @@ -1,4 +1,5 @@ import { Invoice, Revenue } from './definitions'; +import { fetchLatestInvoices } from './data-fetches'; export const calculateAllInvoices = ( invoices: Invoice[], @@ -19,7 +20,7 @@ export const calculateCustomerInvoices = ( customerId: number, ) => { return invoices - .filter((invoice) => invoice.customerId === customerId) + .filter((invoice) => invoice.customer_id === customerId) .filter((invoice) => !status || invoice.status === status) .reduce((total, invoice) => total + invoice.amount / 100, 0) .toLocaleString('en-US', { @@ -38,13 +39,12 @@ export const countCustomerInvoices = ( invoices: Invoice[], customerId: number, ) => { - return invoices.filter((invoice) => invoice.customerId === customerId).length; + return invoices.filter((invoice) => invoice.customer_id === customerId) + .length; }; -export const findLatestInvoices = (invoices: Invoice[]) => { - return [...invoices] - .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) - .slice(0, 5); +export const findLatestInvoices = async () => { + return await fetchLatestInvoices(); }; export const generateYAxis = (revenue: Revenue[]) => { diff --git a/dashboard/15-final/app/lib/data-fetches.ts b/dashboard/15-final/app/lib/data-fetches.ts new file mode 100644 index 0000000..94f5f84 --- /dev/null +++ b/dashboard/15-final/app/lib/data-fetches.ts @@ -0,0 +1,65 @@ +import { sql } from '@vercel/postgres'; + +export async function fetchAllInvoices() { + const invoicesData = await sql`SELECT * FROM invoices`; + return invoicesData.rows; +} + +export async function fetchAllCustomers() { + const customersData = await sql`SELECT * FROM customers`; + return customersData.rows; +} + +export async function fetchAllRevenue() { + const revenueData = await sql`SELECT * FROM revenue`; + return revenueData.rows; +} + +export async function fetchFilteredInvoices( + searchTerm: string, + currentPage: number, + ITEMS_PER_PAGE: number, +) { + const invoicesData = await sql` + SELECT + invoices.*, + customers.name AS customer_name, + customers.email AS customer_email, + customers.image_url AS customer_image + FROM + invoices + JOIN + customers ON invoices.customer_id = customers.id + WHERE + invoices.id::text ILIKE ${`%${searchTerm}%`} OR + customers.name ILIKE ${`%${searchTerm}%`} OR + customers.email ILIKE ${`%${searchTerm}%`} OR + invoices.amount::text ILIKE ${`%${searchTerm}%`} OR + invoices.date::text ILIKE ${`%${searchTerm}%`} OR + invoices.status ILIKE ${`%${searchTerm}%`} + LIMIT ${ITEMS_PER_PAGE} + OFFSET ${(currentPage - 1) * ITEMS_PER_PAGE} + `; + return invoicesData.rows; +} + +export async function fetchInvoiceCountBySearchTerm(searchTerm: string) { + const { rows: countRows } = await sql` + SELECT COUNT(*) + FROM invoices + LEFT JOIN customers ON invoices.customer_id = customers.id + WHERE (invoices.id::text ILIKE ${`%${searchTerm}%`} OR customers.name ILIKE ${`%${searchTerm}%`} OR customers.email ILIKE ${`%${searchTerm}%`}) +`; + return countRows[0].count; +} + +export async function fetchInvoiceById(id: number | null) { + return await sql`SELECT * from INVOICES where id=${id}`; +} + +export async function fetchLatestInvoices() { + const latestInvoices = await sql`SELECT * FROM invoices + ORDER BY date DESC + LIMIT 5;`; + return latestInvoices.rows; +} diff --git a/dashboard/15-final/app/lib/definitions.tsx b/dashboard/15-final/app/lib/definitions.ts similarity index 93% rename from dashboard/15-final/app/lib/definitions.tsx rename to dashboard/15-final/app/lib/definitions.ts index a4c51a3..bbb5675 100644 --- a/dashboard/15-final/app/lib/definitions.tsx +++ b/dashboard/15-final/app/lib/definitions.ts @@ -12,12 +12,12 @@ export type Customer = { id: number; name: string; email: string; - imageUrl: string; + image_url: string; }; export type Invoice = { id: number; - customerId: number; + customer_id: number; amount: number; // In TypeScript, this is called a string union type. // It means that the "status" property can only be one of the two strings: 'pending' or 'paid'. diff --git a/dashboard/15-final/app/lib/dummy-data.tsx b/dashboard/15-final/app/lib/dummy-data.js similarity index 76% rename from dashboard/15-final/app/lib/dummy-data.tsx rename to dashboard/15-final/app/lib/dummy-data.js index 4cc9669..38a7c89 100644 --- a/dashboard/15-final/app/lib/dummy-data.tsx +++ b/dashboard/15-final/app/lib/dummy-data.js @@ -1,7 +1,5 @@ -import { User, Customer, Invoice, Revenue } from './definitions'; - // This file contains dummy data that you'll be replacing with real data in Chapter 7. -export const users: User[] = [ +const users = [ { id: 1, name: 'User', @@ -10,142 +8,142 @@ export const users: User[] = [ }, ]; -export const customers: Customer[] = [ +const customers = [ { id: 1, name: 'Ada Lovelace', email: 'ada@lovelace.com', - imageUrl: '/customers/ada-lovelace.png', + image_url: '/customers/ada-lovelace.png', }, { id: 2, name: 'Grace Hopper', email: 'grace@hopper.com', - imageUrl: '/customers/grace-hopper.png', + image_url: '/customers/grace-hopper.png', }, { id: 3, name: 'Hedy Lammar', email: 'hedy@lammar.com', - imageUrl: '/customers/hedy-lammar.png', + image_url: '/customers/hedy-lammar.png', }, { id: 4, name: 'Margaret Hamilton', email: 'margaret@hamilton.com', - imageUrl: '/customers/margaret-hamilton.png', + image_url: '/customers/margaret-hamilton.png', }, ]; -export const invoices: Invoice[] = [ +const invoices = [ { id: 1, - customerId: 1, + customer_id: 1, amount: 15795, status: 'pending', date: '2023-12-06', }, { id: 2, - customerId: 2, + customer_id: 2, amount: 20348, status: 'pending', date: '2023-11-14', }, { id: 3, - customerId: 3, + customer_id: 3, amount: 3040, status: 'paid', date: '2023-10-29', }, { id: 4, - customerId: 4, + customer_id: 4, amount: 44800, status: 'paid', date: '2023-09-10', }, { id: 5, - customerId: 1, + customer_id: 1, amount: 34577, status: 'pending', date: '2023-08-05', }, { id: 6, - customerId: 2, + customer_id: 2, amount: 54246, status: 'pending', date: '2023-07-16', }, { id: 7, - customerId: 3, + customer_id: 3, amount: 8945, status: 'pending', date: '2023-06-27', }, { id: 8, - customerId: 4, + customer_id: 4, amount: 32545, status: 'paid', date: '2023-06-09', }, { id: 9, - customerId: 3, + customer_id: 3, amount: 1250, status: 'paid', date: '2023-06-17', }, { id: 10, - customerId: 1, + customer_id: 1, amount: 8945, status: 'paid', date: '2023-06-07', }, { id: 11, - customerId: 2, + customer_id: 2, amount: 500, status: 'paid', date: '2023-08-19', }, { id: 12, - customerId: 3, + customer_id: 3, amount: 8945, status: 'paid', date: '2023-06-03', }, { id: 13, - customerId: 3, + customer_id: 3, amount: 8945, status: 'paid', date: '2023-06-18', }, { id: 14, - customerId: 4, + customer_id: 4, amount: 8945, status: 'paid', date: '2023-10-04', }, { id: 15, - customerId: 3, + customer_id: 3, amount: 1000, status: 'paid', date: '2022-06-05', }, ]; -export const revenue: Revenue[] = [ +const revenue = [ { month: 'Jan', revenue: 2000 }, { month: 'Feb', revenue: 1800 }, { month: 'Mar', revenue: 2200 }, @@ -159,3 +157,10 @@ export const revenue: Revenue[] = [ { month: 'Nov', revenue: 3000 }, { month: 'Dec', revenue: 4800 }, ]; + +module.exports = { + users, + customers, + invoices, + revenue, +}; diff --git a/dashboard/15-final/app/ui/customers/table.tsx b/dashboard/15-final/app/ui/customers/table.tsx index 47b65c5..af08b5b 100644 --- a/dashboard/15-final/app/ui/customers/table.tsx +++ b/dashboard/15-final/app/ui/customers/table.tsx @@ -1,11 +1,15 @@ -import { customers, invoices } from '@/app/lib/dummy-data'; import { countCustomerInvoices, calculateCustomerInvoices, } from '@/app/lib/calculations'; +import { Customer, Invoice } from '@/app/lib/definitions'; +import { fetchAllCustomers, fetchAllInvoices } from '@/app/lib/data-fetches'; import Image from 'next/image'; -export default function CustomersTable() { +export default async function CustomersTable() { + const invoices = (await fetchAllInvoices()) as Invoice[]; + const customers = (await fetchAllCustomers()) as Customer[]; + return (
@@ -42,7 +46,7 @@ export default function CustomersTable() {
{customer.name} @@ -18,7 +18,7 @@ export default function LatestInvoices({ {lastFiveInvoices.map((invoice) => { const customer = customers.find( - (customer) => customer.id === invoice.customerId, + (customer) => customer.id === invoice.customer_id, ); return (
{customer?.name customer.id === invoice?.customerId, + (customer) => customer.id === invoice?.customer_id, ); const initialCustomer = customer ? customer.id : 0; const initialAmount = invoice?.amount ? invoice.amount / 100 : 0; @@ -31,7 +31,7 @@ export default function InvoiceForm({ if (selectedCustomer && amount) { const newInvoice: Invoice = { - customerId: selectedCustomer, + customer_id: selectedCustomer, amount: amount * 100, // Convert to cents // These would be generated on the server diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx index e0275b4..3ab7cac 100644 --- a/dashboard/15-final/app/ui/invoices/table.tsx +++ b/dashboard/15-final/app/ui/invoices/table.tsx @@ -1,5 +1,3 @@ -import { invoices, customers } from '@/app/lib/dummy-data'; -import { Customer } from '@/app/lib/definitions'; import Link from 'next/link'; import Image from 'next/image'; import { @@ -10,6 +8,10 @@ import { import DeleteInvoice from '@/app/ui/invoices/delete-button'; import TableSearch from './table-search'; import PaginationButtons from './pagination'; +import { + fetchFilteredInvoices, + fetchInvoiceCountBySearchTerm, +} from '@/app/lib/data-fetches'; const ITEMS_PER_PAGE = 10; @@ -42,7 +44,7 @@ function formatDateToLocal(dateStr: string, locale: string = 'en-US') { return formatter.format(date); } -export default function InvoicesTable({ +export default async function InvoicesTable({ searchParams, }: { searchParams: { @@ -53,35 +55,14 @@ export default function InvoicesTable({ const searchTerm = searchParams.query ?? ''; const currentPage = parseInt(searchParams.page ?? '1'); - const filteredInvoices = invoices.filter((invoice) => { - const customer = getCustomerById(invoice.customerId); - - const invoiceMatches = Object.values(invoice).some( - (value) => - value?.toString().toLowerCase().includes(searchTerm.toLowerCase()), - ); - - const customerMatches = - customer && - Object.values(customer).some( - (value) => - value?.toString().toLowerCase().includes(searchTerm.toLowerCase()), - ); - - return invoiceMatches || customerMatches; - }); - - const paginatedInvoices = filteredInvoices.slice( - (currentPage - 1) * ITEMS_PER_PAGE, - currentPage * ITEMS_PER_PAGE, + const invoices = await fetchFilteredInvoices( + searchTerm, + currentPage, + ITEMS_PER_PAGE, ); - function getCustomerById(customerId: number): Customer | null { - const customer = customers.find((customer) => customer.id === customerId); - return customer ? customer : null; - } - - const totalPages = Math.ceil(filteredInvoices.length / ITEMS_PER_PAGE); + const totalCount = await fetchInvoiceCountBySearchTerm(searchTerm); + const totalPages = Math.ceil(totalCount / ITEMS_PER_PAGE); return (
@@ -132,7 +113,7 @@ export default function InvoicesTable({ - {paginatedInvoices.map((invoice) => ( + {invoices?.map((invoice) => ( {invoice.id} @@ -140,18 +121,17 @@ export default function InvoicesTable({
Customer Image -

{getCustomerById(invoice.customerId)?.name}

+

{invoice.customer_name}

- {getCustomerById(invoice.customerId)?.email} + {invoice.customer_email} {(invoice.amount / 100).toLocaleString('en-US', { diff --git a/dashboard/15-final/package-lock.json b/dashboard/15-final/package-lock.json index 6eb006b..0c0c073 100644 --- a/dashboard/15-final/package-lock.json +++ b/dashboard/15-final/package-lock.json @@ -13,6 +13,7 @@ "@types/node": "20.5.7", "@types/react": "18.2.21", "@types/react-dom": "18.2.7", + "@vercel/postgres": "^0.4.1", "autoprefixer": "10.4.15", "clsx": "^2.0.0", "next": "13.4.19", @@ -22,6 +23,9 @@ "tailwindcss": "3.3.3", "typescript": "5.2.2" }, + "devDependencies": { + "dotenv": "^16.3.1" + }, "engines": { "node": ">=18" } @@ -88,6 +92,14 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@neondatabase/serverless": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.5.6.tgz", + "integrity": "sha512-Ru0lG6W/nQtHRkDFVQFF+1PJYx8wd3jereln0Ep0YkiHey50hjTLVUycQoE4X977605pXMuFWORweuktzph+Xg==", + "dependencies": { + "@types/pg": "8.6.6" + } + }, "node_modules/@next/env": { "version": "13.4.19", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz", @@ -284,6 +296,16 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==" }, + "node_modules/@types/pg": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz", + "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -312,6 +334,20 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, + "node_modules/@vercel/postgres": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@vercel/postgres/-/postgres-0.4.1.tgz", + "integrity": "sha512-rYlNnaXrr2/NWK/OodhAUyed0bomaizKKC8XXjNYv8I1K3m75oocP4IGTcBpZe76VCrHuaKW5d6jLQnuRRoNKg==", + "dependencies": { + "@neondatabase/serverless": "0.5.6", + "bufferutil": "4.0.7", + "utf-8-validate": "6.0.3", + "ws": "8.13.0" + }, + "engines": { + "node": ">=14.6" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -434,6 +470,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -561,6 +609,18 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.503", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz", @@ -921,6 +981,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-gyp-build": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", + "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -979,6 +1049,34 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -1135,6 +1233,41 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -1457,6 +1590,18 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/utf-8-validate": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", + "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1479,6 +1624,34 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yaml": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", @@ -1542,6 +1715,14 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@neondatabase/serverless": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.5.6.tgz", + "integrity": "sha512-Ru0lG6W/nQtHRkDFVQFF+1PJYx8wd3jereln0Ep0YkiHey50hjTLVUycQoE4X977605pXMuFWORweuktzph+Xg==", + "requires": { + "@types/pg": "8.6.6" + } + }, "@next/env": { "version": "13.4.19", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz", @@ -1645,6 +1826,16 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==" }, + "@types/pg": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz", + "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==", + "requires": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1673,6 +1864,17 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, + "@vercel/postgres": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@vercel/postgres/-/postgres-0.4.1.tgz", + "integrity": "sha512-rYlNnaXrr2/NWK/OodhAUyed0bomaizKKC8XXjNYv8I1K3m75oocP4IGTcBpZe76VCrHuaKW5d6jLQnuRRoNKg==", + "requires": { + "@neondatabase/serverless": "0.5.6", + "bufferutil": "4.0.7", + "utf-8-validate": "6.0.3", + "ws": "8.13.0" + } + }, "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -1743,6 +1945,14 @@ "update-browserslist-db": "^1.0.11" } }, + "bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "requires": { + "node-gyp-build": "^4.3.0" + } + }, "busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -1826,6 +2036,12 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true + }, "electron-to-chromium": { "version": "1.4.503", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz", @@ -2075,6 +2291,11 @@ } } }, + "node-gyp-build": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", + "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==" + }, "node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -2118,6 +2339,28 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -2197,6 +2440,29 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2395,6 +2661,14 @@ "picocolors": "^1.0.0" } }, + "utf-8-validate": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", + "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==", + "requires": { + "node-gyp-build": "^4.3.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2414,6 +2688,17 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "requires": {} + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "yaml": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", diff --git a/dashboard/15-final/package.json b/dashboard/15-final/package.json index 7e6b047..1c19d7b 100644 --- a/dashboard/15-final/package.json +++ b/dashboard/15-final/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "next build", "dev": "next dev", + "seed": "node -r dotenv/config ./scripts/seed.js", "start": "next start" }, "dependencies": { @@ -13,6 +14,7 @@ "@types/node": "20.5.7", "@types/react": "18.2.21", "@types/react-dom": "18.2.7", + "@vercel/postgres": "^0.4.1", "autoprefixer": "10.4.15", "clsx": "^2.0.0", "next": "13.4.19", @@ -22,6 +24,9 @@ "tailwindcss": "3.3.3", "typescript": "5.2.2" }, + "devDependencies": { + "dotenv": "^16.3.1" + }, "engines": { "node": ">=18" } diff --git a/dashboard/15-final/scripts/seed.js b/dashboard/15-final/scripts/seed.js new file mode 100644 index 0000000..9a3bfb6 --- /dev/null +++ b/dashboard/15-final/scripts/seed.js @@ -0,0 +1,118 @@ +const { sql } = require('@vercel/postgres'); +const { invoices, customers, revenue } = require('../app/lib/dummy-data.js'); + +async function seedInvoices() { + try { + // Create the "invoices" table if it doesn't exist + const createTable = await sql` + CREATE TABLE IF NOT EXISTS invoices ( + id SERIAL PRIMARY KEY, + customer_id INT NOT NULL, + amount INT NOT NULL, + status VARCHAR(255) NOT NULL, + date DATE NOT NULL + ); + `; + + console.log(`Created "invoices" table`); + + // Insert data into the "invoices" table + const insertedInvoices = await Promise.all( + invoices.map( + (invoice) => sql` + INSERT INTO invoices (id, customer_id, amount, status, date) + VALUES (${invoice.id}, ${invoice.customer_id}, ${invoice.amount}, ${invoice.status}, ${invoice.date}) + ON CONFLICT (id) DO NOTHING; + `, + ), + ); + + console.log(`Seeded ${insertedInvoices.length} invoices`); + + return { + createTable, + invoices: insertedInvoices, + }; + } catch (error) { + console.error('Error seeding invoices:', error); + throw error; + } +} + +async function seedCustomers() { + try { + // Create the "invoices" table if it doesn't exist + const createTable = await sql` + CREATE TABLE IF NOT EXISTS customers ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + image_url VARCHAR(255) NOT NULL + ); + `; + + console.log(`Created "customers" table`); + + // Insert data into the "customers" table + const insertedCustomers = await Promise.all( + customers.map( + (customer) => sql` + INSERT INTO customers (id, name, email, image_url) + VALUES (${customer.id}, ${customer.name}, ${customer.email}, ${customer.image_url}) + ON CONFLICT (id) DO NOTHING; + `, + ), + ); + + console.log(`Seeded ${insertedCustomers.length} customers`); + + return { + createTable, + customers: insertedCustomers, + }; + } catch (error) { + console.error('Error seeding customers:', error); + throw error; + } +} + +async function seedRevenue() { + try { + // Create the "revenue" table if it doesn't exist + const createTable = await sql` + CREATE TABLE IF NOT EXISTS revenue ( + month VARCHAR(4) NOT NULL UNIQUE, + revenue INT NOT NULL + ); + `; + + console.log(`Created "revenue" table`); + + // Insert data into the "revenue" table + const insertedRevenue = await Promise.all( + revenue.map( + (rev) => sql` + INSERT INTO revenue (month, revenue) + VALUES (${rev.month}, ${rev.revenue}) + ON CONFLICT (month) DO NOTHING; + `, + ), + ); + + console.log(`Seeded ${insertedRevenue.length} revenue`); + + return { + createTable, + revenue: insertedRevenue, + }; + } catch (error) { + console.error('Error seeding revenue:', error); + throw error; + } +} + +(async () => { + await seedCustomers(); + await seedInvoices(); + await seedRevenue(); +})(); diff --git a/dashboard/15-final/tsconfig.json b/dashboard/15-final/tsconfig.json index c714696..10ea0dc 100644 --- a/dashboard/15-final/tsconfig.json +++ b/dashboard/15-final/tsconfig.json @@ -22,6 +22,13 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "app/lib/dummy-data.js", + "scripts/seed.js" + ], "exclude": ["node_modules"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2bb290d..656d270 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -224,6 +228,9 @@ importers: '@types/react-dom': specifier: 18.2.7 version: 18.2.7 + '@vercel/postgres': + specifier: ^0.4.1 + version: 0.4.1 autoprefixer: specifier: 10.4.15 version: 10.4.15(postcss@8.4.28) @@ -248,6 +255,10 @@ importers: typescript: specifier: 5.2.2 version: 5.2.2 + devDependencies: + dotenv: + specifier: ^16.3.1 + version: 16.3.1 seo/demo: dependencies: @@ -585,6 +596,12 @@ packages: resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} dev: true + /@neondatabase/serverless@0.5.6: + resolution: {integrity: sha512-Ru0lG6W/nQtHRkDFVQFF+1PJYx8wd3jereln0Ep0YkiHey50hjTLVUycQoE4X977605pXMuFWORweuktzph+Xg==} + dependencies: + '@types/pg': 8.6.6 + dev: false + /@next/env@13.4.19: resolution: {integrity: sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==} dev: false @@ -763,7 +780,6 @@ packages: /@types/node@18.11.9: resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==} - dev: true /@types/node@20.5.7: resolution: {integrity: sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==} @@ -777,6 +793,14 @@ packages: resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} dev: false + /@types/pg@8.6.6: + resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} + dependencies: + '@types/node': 18.11.9 + pg-protocol: 1.6.0 + pg-types: 2.2.0 + dev: false + /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} @@ -997,6 +1021,16 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@vercel/postgres@0.4.1: + resolution: {integrity: sha512-rYlNnaXrr2/NWK/OodhAUyed0bomaizKKC8XXjNYv8I1K3m75oocP4IGTcBpZe76VCrHuaKW5d6jLQnuRRoNKg==} + engines: {node: '>=14.6'} + dependencies: + '@neondatabase/serverless': 0.5.6 + bufferutil: 4.0.7 + utf-8-validate: 6.0.3 + ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) + dev: false + /@vercel/style-guide@5.0.1(eslint@8.48.0)(prettier@3.0.3)(typescript@4.8.4): resolution: {integrity: sha512-3J/5xpwJ2Wk+cKB3EGY2KCdVQycaThLKhjBmgXPfIKb+E74lPpXVIDfaQE0D2JoAyIzGsqdH7Lbmr+DojwofxQ==} engines: {node: '>=16'} @@ -1281,6 +1315,14 @@ packages: node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) + /bufferutil@4.0.7: + resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.1 + dev: false + /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -1576,6 +1618,11 @@ packages: esutils: 2.0.3 dev: true + /dotenv@16.3.1: + resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + engines: {node: '>=12'} + dev: true + /electron-to-chromium@1.4.508: resolution: {integrity: sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==} @@ -3408,6 +3455,11 @@ packages: - babel-plugin-macros dev: false + /node-gyp-build@4.6.1: + resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==} + hasBin: true + dev: false + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} @@ -3648,6 +3700,26 @@ packages: engines: {node: '>=8'} dev: true + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + dev: false + + /pg-protocol@1.6.0: + resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} + dev: false + + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + dev: false + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -3749,6 +3821,28 @@ packages: source-map-js: 1.0.2 dev: false + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + dev: false + + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -4644,6 +4738,14 @@ packages: punycode: 2.3.0 dev: true + /utf-8-validate@6.0.3: + resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.1 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: false @@ -4766,6 +4868,22 @@ packages: /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dependencies: + bufferutil: 4.0.7 + utf-8-validate: 6.0.3 + dev: false + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'}