Rearrange everything and migrate to talking directly to the database

This commit is contained in:
2024-02-11 18:39:39 +00:00
parent 363c2fc678
commit 28b01769e5
15 changed files with 219 additions and 22 deletions
-1
View File
@@ -1 +0,0 @@
+1 -1
View File
@@ -1,4 +1,4 @@
import OverviewPage, {OverviewConfig} from "@/app/OverviewPage";
import OverviewPage, {OverviewConfig} from "@/components/OverviewPage";
const semester1Revision: OverviewConfig = {
title: 'Semester 1 Revision',
+1 -1
View File
@@ -1,4 +1,4 @@
import OverviewPage, {OverviewConfig} from "@/app/OverviewPage";
import OverviewPage, {OverviewConfig} from "@/components/OverviewPage";
const semester2: OverviewConfig = {
title: 'Semester 2',
-4
View File
@@ -1,4 +0,0 @@
export const fetcher = (
input: string | URL | globalThis.Request,
init?: RequestInit,
) => fetch(input, init).then(res => res.json())
@@ -1,7 +1,8 @@
import {SubjectComparisonCard} from "@/app/cards/subjectComparisonCard";
import {CalendarOverviewCard} from "@/app/cards/calendarOverviewCard";
import {SubjectOverviewCard} from "@/app/cards/subjectOverviewCard";
import {getData} from "@/app/fetchData";
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 {
title: string,
@@ -22,7 +23,7 @@ export interface OverviewConfig {
export default async function OverviewPage({config}: {
config: OverviewConfig
}) {
const data = await getData(config);
const data = await getDataSQL(config);
return (
<main className="m-6">
@@ -4,9 +4,9 @@ import * as R from 'ramda';
import * as dFns from 'date-fns';
import CalendarHeatmap from 'react-calendar-heatmap';
import 'react-calendar-heatmap/dist/styles.css';
import '../calendar-styles.css'
import '../../app/calendar-styles.css'
import {Tooltip} from 'react-tooltip';
import {Data} from "@/app/fetchData";
import {Data} from "@/data/fetchData";
const granularity = 4;
@@ -2,7 +2,7 @@
import {Card, DonutChart, Title} from "@tremor/react";
import * as R from "ramda";
import {Data} from "@/app/fetchData";
import {Data} from "@/data/fetchData";
function useBreakdownData(data: Data): {
name: string,
@@ -1,5 +1,5 @@
import {Card, Metric, Text, Title} from "@tremor/react";
import {Data} from "@/app/fetchData";
import {Data} from "@/data/fetchData";
export function SubjectOverviewCard({
title,
+35
View File
@@ -0,0 +1,35 @@
import {DB as Database} from './db' // this is the Database interface we defined earlier
import {Pool} from 'pg'
import {Kysely, PostgresDialect} from 'kysely'
import * as fs from "node:fs";
function fileOrEnv(fileKey: string, valueKey: string): string | undefined {
const file = process.env[fileKey];
if (file && fs.existsSync(file)) {
return fs.readFileSync(file, 'utf8');
}
return process.env[valueKey];
}
function getCredentials() {
return {
user: fileOrEnv('POSTGRES_USER_FILE', 'POSTGRES_USER'),
password: fileOrEnv('POSTGRES_PASSWORD_FILE', 'POSTGRES_PASSWORD'),
}
}
const dialect = new PostgresDialect({
pool: new Pool({
database: process.env.POSTGRES_DB!,
host: process.env.POSTGRES_HOST!,
port: parseInt(process.env.POSTGRES_PORT!),
...getCredentials(),
max: 10,
})
})
export const db = new Kysely<Database>({
dialect,
})
+70
View File
@@ -0,0 +1,70 @@
import type {ColumnType, JSONColumnType} from "kysely";
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;
export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>;
export type Json = ColumnType<JsonValue, string, string>;
export type JsonArray = JsonValue[];
export type JsonObject = {
[K in string]?: JsonValue;
};
export type JsonPrimitive = boolean | number | string | null;
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
export interface Client {
archived: boolean;
at: Timestamp;
id: number;
name: string;
server_deleted_at: Timestamp | null;
workspace_id: number;
}
export interface Project {
active: boolean;
client_id: number | null;
id: Generated<number>;
name: string;
raw_json: JSONColumnType<{
color: string;
id: number;
name: string;
}>;
toggl_id: Int8;
workspace_id: Int8;
}
export interface TimeEntry {
description: string;
id: Generated<number>;
project_id: Int8 | null;
raw_json: JSONColumnType<{
start: string;
end: string;
seconds: number;
}>;
start: Timestamp;
stop: Timestamp;
toggl_id: Int8;
}
export interface TogglPortalSeaqlMigrations {
applied_at: Int8;
version: string;
}
export interface DB {
client: Client;
project: Project;
time_entry: TimeEntry;
toggl_portal_seaql_migrations: TogglPortalSeaqlMigrations;
}
@@ -1,7 +1,5 @@
"use server";
import {OverviewConfig} from "@/app/OverviewPage";
import {OverviewConfig} from "@/components/OverviewPage";
export interface Data {
projects: {
@@ -19,9 +17,9 @@ export interface Data {
}[],
}
export async function getData(config: OverviewConfig) {
export async function getData(config: OverviewConfig): Promise<Data> {
const projectIds = config.subjects.map((subject) => subject.projectId);
const projectResponse = await fetch(`https://revision.joshuacoles.me/api/project?select=raw_json&toggl_id=in.(${projectIds.join(',')})`);
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,
@@ -29,7 +27,7 @@ export async function getData(config: OverviewConfig) {
color: project.raw_json.color,
}));
const timeEntriesResponse = await fetch(`https://revision.joshuacoles.me/api/time_entry?select=project_id,raw_json&project_id=in.(${projectIds.join(',')})&start=gt.${config.timePeriod.start}&start=lt.${config.timePeriod.end}`);
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,
+35
View File
@@ -0,0 +1,35 @@
import {OverviewConfig} from "@/components/OverviewPage";
import {Data} from "@/data/fetchData";
import {db} from "@/data/database";
import * as dFns from "date-fns";
export async function getDataSQL(config: OverviewConfig): Promise<Data> {
let projectIds = config.subjects.map((subject) => subject.projectId.toString());
const projects = await db.selectFrom('project')
.select('raw_json')
.where('project.toggl_id', 'in', projectIds)
.execute();
const timeEntries = await db.selectFrom('time_entry')
.select(['project_id', 'raw_json'])
.where('project_id', 'in', projectIds)
.where('start', '>', dFns.parseISO(config.timePeriod.start))
.where('start', '<', dFns.parseISO(config.timePeriod.end))
.execute();
return {
projects: projects.map((project) => ({
projectId: project.raw_json.id,
name: project.raw_json.name,
color: project.raw_json.color,
})),
timeEntries: timeEntries.map((timeEntry) => ({
projectId: parseInt(timeEntry.project_id!),
start: timeEntry.raw_json.start,
end: timeEntry.raw_json.end,
duration: timeEntry.raw_json.seconds,
})),
}
}