From e967244158dd3491e4db49029e868d44de24a510 Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Sun, 11 Feb 2024 18:59:32 +0000 Subject: [PATCH] Parse postgres connection information out of the environment --- package.json | 4 +- pnpm-lock.yaml | 105 ++++++++++++-------------------- src/components/OverviewPage.tsx | 1 - src/data/database.ts | 49 +++++++++++++-- src/data/fetchData.ts | 27 -------- 5 files changed, 83 insertions(+), 103 deletions(-) diff --git a/package.json b/package.json index d3c8434..f91da59 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "react-dom": "^18", "react-tooltip": "^5.25.0", "reaviz": "^15.2.1", - "swr": "^2.2.4" + "zod": "^3.22.4" }, "devDependencies": { "@testing-library/jest-dom": "^6.1.5", @@ -41,7 +41,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "kysely-codegen": "^0.11.0", - "postcss": "^8", + "postcss": "^8.4.35", "tailwindcss": "^3.3.0", "ts-node": "^10.9.2", "typescript": "^5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e862463..562c7a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,9 +50,9 @@ dependencies: reaviz: specifier: ^15.2.1 version: 15.2.1(@types/react@18.0.0)(prop-types@15.8.1)(react-dom@18.0.0)(react@18.0.0) - swr: - specifier: ^2.2.4 - version: 2.2.4(react@18.0.0) + zod: + specifier: ^3.22.4 + version: 3.22.4 devDependencies: '@testing-library/jest-dom': @@ -78,7 +78,7 @@ devDependencies: version: 18.0.0 autoprefixer: specifier: ^10.0.1 - version: 10.0.1(postcss@8.0.0) + version: 10.0.1(postcss@8.4.35) eslint: specifier: ^8 version: 8.0.0 @@ -95,11 +95,11 @@ devDependencies: specifier: ^0.11.0 version: 0.11.0(kysely@0.27.2)(pg@8.11.3) postcss: - specifier: ^8 - version: 8.0.0 + specifier: ^8.4.35 + version: 8.4.35 tailwindcss: specifier: ^3.3.0 - version: 3.3.0(postcss@8.0.0)(ts-node@10.9.2) + version: 3.3.0(postcss@8.4.35)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.0.0)(typescript@5.0.2) @@ -543,7 +543,7 @@ packages: peerDependencies: tailwindcss: ^3.0 dependencies: - tailwindcss: 3.3.0(postcss@8.0.0)(ts-node@10.9.2) + tailwindcss: 3.3.0(postcss@8.4.35)(ts-node@10.9.2) dev: false /@heroicons/react@1.0.6(react@18.0.0): @@ -1810,7 +1810,7 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: true - /autoprefixer@10.0.1(postcss@8.0.0): + /autoprefixer@10.0.1(postcss@8.4.35): resolution: {integrity: sha512-aQo2BDIsoOdemXUAOBpFv4ZQa2DrOtEufarYhtFsK1088Ca0TUwu/aQWf0M3mrILXZ3mTIVn1lR3hPW8acacsw==} engines: {node: ^10 || ^12 || >=14} hasBin: true @@ -1822,7 +1822,7 @@ packages: colorette: 1.4.0 normalize-range: 0.1.2 num2fraction: 1.2.2 - postcss: 8.0.0 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true @@ -2146,6 +2146,7 @@ packages: /colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + dev: true /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} @@ -3788,21 +3789,12 @@ packages: get-intrinsic: 1.2.4 dev: true - /isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - /isobject@2.1.0: - resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} - engines: {node: '>=0.10.0'} - dependencies: - isarray: 1.0.0 - /istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -4518,12 +4510,6 @@ packages: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} - /line-column@1.0.2: - resolution: {integrity: sha512-Ktrjk5noGYlHsVnYWh62FLVs4hTb8A3e+vucNZMgPeAOITdshMSgv4cCZQeRDjm7+goqmo6+liZwTXo+U3sVww==} - dependencies: - isarray: 1.0.0 - isobject: 2.1.0 - /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -5087,27 +5073,27 @@ packages: deprecated: You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1 dev: false - /postcss-import@14.1.0(postcss@8.0.0): + /postcss-import@14.1.0(postcss@8.4.35): resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.0.0 + postcss: 8.4.35 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - /postcss-js@4.0.1(postcss@8.0.0): + /postcss-js@4.0.1(postcss@8.4.35): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 dependencies: camelcase-css: 2.0.1 - postcss: 8.0.0 + postcss: 8.4.35 - /postcss-load-config@3.1.4(postcss@8.0.0)(ts-node@10.9.2): + /postcss-load-config@3.1.4(postcss@8.4.35)(ts-node@10.9.2): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} peerDependencies: @@ -5120,17 +5106,17 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - postcss: 8.0.0 + postcss: 8.4.35 ts-node: 10.9.2(@types/node@20.0.0)(typescript@5.0.2) yaml: 1.10.2 - /postcss-nested@6.0.0(postcss@8.0.0): + /postcss-nested@6.0.0(postcss@8.4.35): resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.0.0 + postcss: 8.4.35 postcss-selector-parser: 6.0.15 /postcss-selector-parser@6.0.15: @@ -5143,15 +5129,6 @@ packages: /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - /postcss@8.0.0: - resolution: {integrity: sha512-BriaW5AeZHfyuuKhK3Z6yRDKI6NR2TdRWyZcj3+Pk2nczQsMBqavggAzTledsbyexPthW3nFA6XfgCWjZqmVPA==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - colorette: 1.4.0 - line-column: 1.0.2 - nanoid: 3.3.7 - source-map: 0.6.1 - /postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -5161,6 +5138,14 @@ packages: source-map-js: 1.0.2 dev: false + /postcss@8.4.35: + resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -5815,7 +5800,6 @@ packages: /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - dev: false /source-map-support@0.3.3: resolution: {integrity: sha512-9O4+y9n64RewmFoKUZ/5Tx9IHIcXM6Q+RTSw6ehnqybUz4a7iwR3Eaw80uLtqqQ5D0C+5H03D4KKGo9PdP33Gg==} @@ -5845,6 +5829,7 @@ packages: /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + dev: true /split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} @@ -6063,16 +6048,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /swr@2.2.4(react@18.0.0): - resolution: {integrity: sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 - dependencies: - client-only: 0.0.1 - react: 18.0.0 - use-sync-external-store: 1.2.0(react@18.0.0) - dev: false - /symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true @@ -6093,7 +6068,7 @@ packages: resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} dev: false - /tailwindcss@3.3.0(postcss@8.0.0)(ts-node@10.9.2): + /tailwindcss@3.3.0(postcss@8.4.35)(ts-node@10.9.2): resolution: {integrity: sha512-hOXlFx+YcklJ8kXiCAfk/FMyr4Pm9ck477G0m/us2344Vuj355IpoEDB5UmGAsSpTBmr+4ZhjzW04JuFXkb/fw==} engines: {node: '>=12.13.0'} hasBin: true @@ -6114,11 +6089,11 @@ packages: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.0.0 - postcss-import: 14.1.0(postcss@8.0.0) - postcss-js: 4.0.1(postcss@8.0.0) - postcss-load-config: 3.1.4(postcss@8.0.0)(ts-node@10.9.2) - postcss-nested: 6.0.0(postcss@8.0.0) + postcss: 8.4.35 + postcss-import: 14.1.0(postcss@8.4.35) + postcss-js: 4.0.1(postcss@8.4.35) + postcss-load-config: 3.1.4(postcss@8.4.35)(ts-node@10.9.2) + postcss-nested: 6.0.0(postcss@8.4.35) postcss-selector-parser: 6.0.15 postcss-value-parser: 4.2.0 quick-lru: 5.1.1 @@ -6452,14 +6427,6 @@ packages: use-isomorphic-layout-effect: 1.1.2(@types/react@18.0.0)(react@18.0.0) dev: false - /use-sync-external-store@1.2.0(react@18.0.0): - resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.0.0 - dev: false - /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -6703,3 +6670,7 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + dev: false diff --git a/src/components/OverviewPage.tsx b/src/components/OverviewPage.tsx index e81e86a..134fc79 100644 --- a/src/components/OverviewPage.tsx +++ b/src/components/OverviewPage.tsx @@ -1,7 +1,6 @@ import {SubjectComparisonCard} from "@/components/cards/subjectComparisonCard"; import {CalendarOverviewCard} from "@/components/cards/calendarOverviewCard"; import {SubjectOverviewCard} from "@/components/cards/subjectOverviewCard"; -import {getData} from "@/data/fetchData"; import {getDataSQL} from "@/data/fetchWithSQL"; export interface OverviewConfig { diff --git a/src/data/database.ts b/src/data/database.ts index d85a827..0536635 100644 --- a/src/data/database.ts +++ b/src/data/database.ts @@ -2,15 +2,52 @@ import {DB as Database} from './db' // this is the Database interface we defined import {Pool} from 'pg' import {Kysely, PostgresDialect} from 'kysely' import * as fs from "node:fs"; +import {z} from 'zod'; -function fileOrEnv(fileKey: string, valueKey: string): string | undefined { - const file = process.env[fileKey]; +const envSchema = z.object({ + POSTGRES_DB: z.string(), + POSTGRES_HOST: z.string(), + POSTGRES_PORT: z.string().transform((val, ctx) => { + const parsed = parseInt(val); + if (isNaN(parsed)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Not a number", + }); + + return z.NEVER; + } + return parsed; + }), + + POSTGRES_USER: z.string().optional(), + POSTGRES_USER_FILE: z.string().optional(), + POSTGRES_PASSWORD: z.string().optional(), + POSTGRES_PASSWORD_FILE: z.string().optional(), +}).refine( + env => env.POSTGRES_USER || env.POSTGRES_USER_FILE, + { + message: "Either POSTGRES_USER or POSTGRES_USER_FILE must be set", + path: ["POSTGRES_USER", "POSTGRES_USER_FILE"], + } +).refine( + env => env.POSTGRES_PASSWORD || env.POSTGRES_PASSWORD_FILE, + { + message: "Either POSTGRES_PASSWORD or POSTGRES_PASSWORD_FILE must be set", + path: ["POSTGRES_PASSWORD", "POSTGRES_PASSWORD_FILE"], + } +); + +const env = envSchema.parse(process.env); + +function fileOrEnv(fileKey: keyof typeof env, valueKey: keyof typeof env): string | undefined { + const file: string = env[fileKey] as string; if (file && fs.existsSync(file)) { return fs.readFileSync(file, 'utf8'); } - return process.env[valueKey]; + return env[valueKey] as string; } function getCredentials() { @@ -22,9 +59,9 @@ function getCredentials() { const dialect = new PostgresDialect({ pool: new Pool({ - database: process.env.POSTGRES_DB!, - host: process.env.POSTGRES_HOST!, - port: parseInt(process.env.POSTGRES_PORT!), + database: env.POSTGRES_DB, + host: env.POSTGRES_HOST, + port: env.POSTGRES_PORT, ...getCredentials(), max: 10, }) diff --git a/src/data/fetchData.ts b/src/data/fetchData.ts index 0765cb5..b628428 100644 --- a/src/data/fetchData.ts +++ b/src/data/fetchData.ts @@ -1,5 +1,4 @@ "use server"; -import {OverviewConfig} from "@/components/OverviewPage"; export interface Data { projects: { @@ -16,29 +15,3 @@ export interface Data { // description: string, }[], } - -export async function getData(config: OverviewConfig): Promise { - const projectIds = config.subjects.map((subject) => subject.projectId); - const projectResponse = await fetch(`https://cosmos.tail307fc.ts.net/api/project?select=raw_json&toggl_id=in.(${projectIds.join(',')})`); - const projects = await projectResponse.json(); - const projectLensed = projects.map((project: any) => ({ - projectId: project.raw_json.id, - name: project.raw_json.name, - color: project.raw_json.color, - })); - - const timeEntriesResponse = await fetch(`https://cosmos.tail307fc.ts.net/api/time_entry?select=project_id,raw_json&project_id=in.(${projectIds.join(',')})&start=gt.${config.timePeriod.start}&start=lt.${config.timePeriod.end}`); - const timeEntries = await timeEntriesResponse.json(); - const timeEntriesLensed = timeEntries.map((timeEntry: any) => ({ - projectId: timeEntry.project_id, - start: timeEntry.raw_json.start, - end: timeEntry.raw_json.end, - duration: timeEntry.raw_json.seconds, - // description: timeEntry.raw_json.description, - })); - - return { - projects: projectLensed, - timeEntries: timeEntriesLensed, - }; -}