From 3ed9a39713d86e7f734203c6a75370c98a6e4668 Mon Sep 17 00:00:00 2001 From: Nutthapat Pongtanyavichai Date: Sun, 13 Oct 2024 15:18:19 +0700 Subject: [PATCH] db: normalize courseInfo --- package.json | 2 +- ..._santerians.sql => 0000_damp_lilandra.sql} | 29 ++-- .../database/drizzle/meta/0000_snapshot.json | 148 ++++++++++-------- packages/database/drizzle/meta/_journal.json | 4 +- packages/database/src/schema/courseData.ts | 73 ++++----- packages/database/src/schema/types.ts | 12 ++ packages/database/src/seed/steps/courses.ts | 56 +++++-- 7 files changed, 196 insertions(+), 128 deletions(-) rename packages/database/drizzle/{0000_shocking_the_santerians.sql => 0000_damp_lilandra.sql} (93%) diff --git a/package.json b/package.json index dc2c9bc5b..37dc84721 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,5 @@ "prettier": "^3.3.3", "turbo": "^2.1.2" }, - "packageManager": "pnpm@9.11.0" + "packageManager": "pnpm@9.12.1" } diff --git a/packages/database/drizzle/0000_shocking_the_santerians.sql b/packages/database/drizzle/0000_damp_lilandra.sql similarity index 93% rename from packages/database/drizzle/0000_shocking_the_santerians.sql rename to packages/database/drizzle/0000_damp_lilandra.sql index 79c240eb0..74c701463 100644 --- a/packages/database/drizzle/0000_shocking_the_santerians.sql +++ b/packages/database/drizzle/0000_damp_lilandra.sql @@ -40,27 +40,30 @@ CREATE TABLE IF NOT EXISTS "course" ( "academic_year" integer NOT NULL, "semester" "semester" NOT NULL, "course_no" text NOT NULL, - "abbr_name" text NOT NULL, - "course_name_en" text NOT NULL, - "course_name_th" text NOT NULL, - "course_desc_en" text, - "course_desc_th" text, - "faculty" text NOT NULL, - "department" text NOT NULL, - "credit" numeric NOT NULL, - "credit_hours" text NOT NULL, "course_condition" text, "midterm_start" timestamp, "midterm_end" timestamp, "final_start" timestamp, "final_end" timestamp, "gen_ed_type" "gen_ed_type" DEFAULT 'NO' NOT NULL, - "rating" double precision, "created_at" timestamp DEFAULT now() NOT NULL, "updated_at" timestamp DEFAULT now() NOT NULL, CONSTRAINT "course_unique" UNIQUE("study_program","academic_year","semester","course_no") ); --> statement-breakpoint +CREATE TABLE IF NOT EXISTS "course_info" ( + "course_no" text PRIMARY KEY NOT NULL, + "abbr_name" text NOT NULL, + "course_name_en" text NOT NULL, + "course_name_th" text NOT NULL, + "course_desc_en" text, + "course_desc_th" text, + "faculty" text NOT NULL, + "department" text NOT NULL, + "credit" numeric NOT NULL, + "credit_hours" text NOT NULL +); +--> statement-breakpoint CREATE TABLE IF NOT EXISTS "course_section" ( "id" text PRIMARY KEY NOT NULL, "course_id" text NOT NULL, @@ -146,6 +149,12 @@ CREATE TABLE IF NOT EXISTS "user" ( CONSTRAINT "user_google_id_unique" UNIQUE("google_id") ); --> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "course" ADD CONSTRAINT "course_course_no_course_info_course_no_fk" FOREIGN KEY ("course_no") REFERENCES "public"."course_info"("course_no") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint DO $$ BEGIN ALTER TABLE "course_section" ADD CONSTRAINT "course_section_course_id_course_id_fk" FOREIGN KEY ("course_id") REFERENCES "public"."course"("id") ON DELETE no action ON UPDATE no action; EXCEPTION diff --git a/packages/database/drizzle/meta/0000_snapshot.json b/packages/database/drizzle/meta/0000_snapshot.json index ade8dfb89..d4339e763 100644 --- a/packages/database/drizzle/meta/0000_snapshot.json +++ b/packages/database/drizzle/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "e865a62d-1e0a-4578-9cfd-b44fbbee2d6f", + "id": "101832e8-626d-434f-bbb9-fd1b0f87f781", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -40,60 +40,6 @@ "primaryKey": false, "notNull": true }, - "abbr_name": { - "name": "abbr_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "course_name_en": { - "name": "course_name_en", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "course_name_th": { - "name": "course_name_th", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "course_desc_en": { - "name": "course_desc_en", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "course_desc_th": { - "name": "course_desc_th", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "faculty": { - "name": "faculty", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "department": { - "name": "department", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credit": { - "name": "credit", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "credit_hours": { - "name": "credit_hours", - "type": "text", - "primaryKey": false, - "notNull": true - }, "course_condition": { "name": "course_condition", "type": "text", @@ -132,12 +78,6 @@ "notNull": true, "default": "'NO'" }, - "rating": { - "name": "rating", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, "created_at": { "name": "created_at", "type": "timestamp", @@ -154,7 +94,21 @@ } }, "indexes": {}, - "foreignKeys": {}, + "foreignKeys": { + "course_course_no_course_info_course_no_fk": { + "name": "course_course_no_course_info_course_no_fk", + "tableFrom": "course", + "tableTo": "course_info", + "columnsFrom": [ + "course_no" + ], + "columnsTo": [ + "course_no" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, "compositePrimaryKeys": {}, "uniqueConstraints": { "course_unique": { @@ -169,6 +123,76 @@ } } }, + "public.course_info": { + "name": "course_info", + "schema": "", + "columns": { + "course_no": { + "name": "course_no", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "abbr_name": { + "name": "abbr_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_name_en": { + "name": "course_name_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_name_th": { + "name": "course_name_th", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_desc_en": { + "name": "course_desc_en", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "course_desc_th": { + "name": "course_desc_th", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "faculty": { + "name": "faculty", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "department": { + "name": "department", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credit": { + "name": "credit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "credit_hours": { + "name": "credit_hours", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, "public.course_section": { "name": "course_section", "schema": "", diff --git a/packages/database/drizzle/meta/_journal.json b/packages/database/drizzle/meta/_journal.json index 55934f9c8..93ae1192c 100644 --- a/packages/database/drizzle/meta/_journal.json +++ b/packages/database/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1727199154437, - "tag": "0000_shocking_the_santerians", + "when": 1728806337421, + "tag": "0000_damp_lilandra", "breakpoints": true } ] diff --git a/packages/database/src/schema/courseData.ts b/packages/database/src/schema/courseData.ts index 77fb06871..a87539d04 100644 --- a/packages/database/src/schema/courseData.ts +++ b/packages/database/src/schema/courseData.ts @@ -2,16 +2,14 @@ import { createId } from '@paralleldrive/cuid2' import { boolean, decimal, - doublePrecision, integer, - pgEnum, pgTable, text, timestamp, unique, } from 'drizzle-orm/pg-core' -import { genEdType, semester, studyProgram } from './types.js' +import { dayOfWeek, genEdType, semester, studyProgram } from './types.js' /** * https://datagateway.chula.ac.th (Connect to Chula Wi-Fi) @@ -34,29 +32,9 @@ export const course = pgTable( semester: semester('semester').notNull(), // 3 COURSECODE / 11 courseno - courseNo: text('course_no').notNull(), - // 4 COURSENAME - abbrName: text('abbr_name').notNull(), - - // 14 coursename_en - courseNameEn: text('course_name_en').notNull(), - // 13 coursename_th - courseNameTh: text('course_name_th').notNull(), - - // 23 coursedescription_en - courseDescEn: text('course_desc_en'), - // 22 coursedescription_th - courseDescTh: text('course_desc_th'), - - // indirect -> 30 FACCODE or From CourseNo (Transitive Dependency) - faculty: text('faculty').notNull(), - // indirect -> From CourseNo (Transitive Dependency) - department: text('department').notNull(), - - // 24 TOTALCREDIT - credit: decimal('credit').notNull(), - // Must build from 17 lcredit 18 nlcredit 19 lhour 20 nlhour 21 shour - creditHours: text('credit_hours').notNull(), + courseNo: text('course_no') + .notNull() + .references(() => courseInfo.courseNo), // ! not available in data gateway courseCondition: text('course_condition'), @@ -72,9 +50,6 @@ export const course = pgTable( // From section rows OR from gened override? genEdType: genEdType('gen_ed_type').notNull().default('NO'), - // CU Get Reg's User Data - rating: doublePrecision('rating'), - createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at') .notNull() @@ -91,6 +66,34 @@ export const course = pgTable( }), ) +export const courseInfo = pgTable('course_info', { + courseNo: text('course_no').primaryKey(), + + // 4 COURSENAME + abbrName: text('abbr_name').notNull(), + + // 14 coursename_en + courseNameEn: text('course_name_en').notNull(), + // 13 coursename_th + courseNameTh: text('course_name_th').notNull(), + + // 23 coursedescription_en + courseDescEn: text('course_desc_en'), + // 22 coursedescription_th + courseDescTh: text('course_desc_th'), + + // TODO separate this into other table + // indirect -> 30 FACCODE or From CourseNo (Transitive Dependency) + faculty: text('faculty').notNull(), + // indirect -> From CourseNo (Transitive Dependency) + department: text('department').notNull(), + + // 24 TOTALCREDIT + credit: decimal('credit').notNull(), + // Must build from 17 lcredit 18 nlcredit 19 lhour 20 nlhour 21 shour + creditHours: text('credit_hours').notNull(), +}) + export const section = pgTable( 'course_section', { @@ -127,18 +130,6 @@ export const section = pgTable( }), ) -export const dayOfWeek = pgEnum('day_of_week', [ - 'MO', - 'TU', - 'WE', - 'TH', - 'FR', - 'SA', - 'SU', - 'AR', - 'IA', -]) - export const sectionClass = pgTable('course_class', { id: text('id') .primaryKey() diff --git a/packages/database/src/schema/types.ts b/packages/database/src/schema/types.ts index 9f12390d1..65d61867a 100644 --- a/packages/database/src/schema/types.ts +++ b/packages/database/src/schema/types.ts @@ -3,3 +3,15 @@ import { pgEnum } from 'drizzle-orm/pg-core' export const semester = pgEnum('semester', ['1', '2', '3']) export const studyProgram = pgEnum('study_program', ['S', 'T', 'I']) export const genEdType = pgEnum('gen_ed_type', ['NO', 'SC', 'SO', 'HU', 'IN']) + +export const dayOfWeek = pgEnum('day_of_week', [ + 'MO', + 'TU', + 'WE', + 'TH', + 'FR', + 'SA', + 'SU', + 'AR', + 'IA', +]) diff --git a/packages/database/src/seed/steps/courses.ts b/packages/database/src/seed/steps/courses.ts index 841d1f1c4..4c7dbecf1 100644 --- a/packages/database/src/seed/steps/courses.ts +++ b/packages/database/src/seed/steps/courses.ts @@ -1,13 +1,53 @@ import { PgInsertValue } from 'drizzle-orm/pg-core' -import { course } from '../../schema/courseData.js' +import { course, courseInfo } from '../../schema/courseData.js' import { db } from '../utils/client.js' import { withTimeLog } from '../utils/log.js' import { parsePeriod } from '../utils/parsing.js' import { courseData } from './_shared.js' -export const seedCourses = () => - withTimeLog('Seed Courses: Total', async () => { +export const seedCourses = async () => { + await withTimeLog('Seed Courses Info: Total', async () => { + const payload = await withTimeLog( + 'Seed Courses Info: Payload', + async () => { + const courseInfoMap: Record< + string, + PgInsertValue + > = {} + + for (const course of courseData) { + courseInfoMap[course.courseNo] = { + courseNo: course.courseNo, + abbrName: course.abbrName, + courseNameEn: course.courseNameEn, + courseNameTh: course.courseNameTh, + courseDescEn: course.courseDescEn, + courseDescTh: course.courseDescTh, + faculty: course.faculty, + department: course.department, + credit: String(course.credit), + creditHours: course.creditHours, + } + } + + const payload: PgInsertValue[] = + Object.values(courseInfoMap) + return payload + }, + ) + + await withTimeLog('Seed Courses Info: Push', async () => { + let index = 0 + while (index < payload.length) { + const next = index + 100 + await db.insert(courseInfo).values(payload.slice(index, next)) + index = next + } + }) + }) + + await withTimeLog('Seed Courses: Total', async () => { const payload = await withTimeLog('Seed Courses: Payload', async () => { const payload: PgInsertValue[] = [] @@ -20,15 +60,6 @@ export const seedCourses = () => academicYear: +course.academicYear, semester: course.semester, courseNo: course.courseNo, - abbrName: course.abbrName, - courseNameEn: course.courseNameEn, - courseNameTh: course.courseNameTh, - courseDescEn: course.courseDescEn, - courseDescTh: course.courseDescTh, - faculty: course.faculty, - department: course.department, - credit: String(course.credit), - creditHours: course.creditHours, courseCondition: course.courseCondition, midtermStart: midterm.start, midtermEnd: midterm.end, @@ -50,3 +81,4 @@ export const seedCourses = () => } }) }) +}