From 6dd7a36b4faf482ea2aec2077ddd783fe35e0105 Mon Sep 17 00:00:00 2001 From: uxiun Date: Wed, 8 Jan 2025 14:09:03 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=E6=99=82=E9=96=93=E5=89=B2=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=20(yet=20error)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration.sql | 47 ++++++++++++++++ .../migration.sql | 21 ++++++++ prisma/schema.prisma | 53 ++++++++++++++++++- src/app/adjust/client.tsx | 7 +-- src/app/adjust/page.tsx | 2 +- src/components/ImportFileButton.tsx | 6 +++ src/lib/course.ts | 3 ++ src/lib/prisma.ts | 27 +++++++++- src/lib/server.ts | 8 +++ 9 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 prisma/migrations/20250108040947_added_courses_table/migration.sql create mode 100644 prisma/migrations/20250108041904_added_day_module_enum/migration.sql create mode 100644 src/lib/server.ts diff --git a/prisma/migrations/20250108040947_added_courses_table/migration.sql b/prisma/migrations/20250108040947_added_courses_table/migration.sql new file mode 100644 index 0000000..28c1752 --- /dev/null +++ b/prisma/migrations/20250108040947_added_courses_table/migration.sql @@ -0,0 +1,47 @@ +-- CreateTable +CREATE TABLE "Course" ( + "code" TEXT NOT NULL, + "name" TEXT NOT NULL, + "credits" INTEGER NOT NULL, + "overview" TEXT NOT NULL, + "remarks" TEXT NOT NULL, + "type" INTEGER NOT NULL, + "recommendedGrade" INTEGER[], + + CONSTRAINT "Course_pkey" PRIMARY KEY ("code") +); + +-- CreateTable +CREATE TABLE "CourseSchedule" ( + "module" INTEGER NOT NULL, + "day" INTEGER NOT NULL, + "period" INTEGER NOT NULL, + "room" TEXT NOT NULL, + "courseCode" TEXT NOT NULL, + + CONSTRAINT "CourseSchedule_pkey" PRIMARY KEY ("module","day","period","room") +); + +-- CreateTable +CREATE TABLE "_CourseToUser" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "Course_code_key" ON "Course"("code"); + +-- CreateIndex +CREATE UNIQUE INDEX "_CourseToUser_AB_unique" ON "_CourseToUser"("A", "B"); + +-- CreateIndex +CREATE INDEX "_CourseToUser_B_index" ON "_CourseToUser"("B"); + +-- AddForeignKey +ALTER TABLE "CourseSchedule" ADD CONSTRAINT "CourseSchedule_courseCode_fkey" FOREIGN KEY ("courseCode") REFERENCES "Course"("code") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CourseToUser" ADD CONSTRAINT "_CourseToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Course"("code") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CourseToUser" ADD CONSTRAINT "_CourseToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20250108041904_added_day_module_enum/migration.sql b/prisma/migrations/20250108041904_added_day_module_enum/migration.sql new file mode 100644 index 0000000..4bf360e --- /dev/null +++ b/prisma/migrations/20250108041904_added_day_module_enum/migration.sql @@ -0,0 +1,21 @@ +/* + Warnings: + + - The primary key for the `CourseSchedule` table will be changed. If it partially fails, the table could be left without primary key constraint. + - Changed the type of `module` on the `CourseSchedule` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + - Changed the type of `day` on the `CourseSchedule` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + +*/ +-- CreateEnum +CREATE TYPE "Module" AS ENUM ('SpringA', 'SpringB', 'SpringC', 'FallA', 'FallB', 'FallC', 'SummerVacation', 'SpringVacation', 'Annual', 'Unknown'); + +-- CreateEnum +CREATE TYPE "Day" AS ENUM ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Intensive', 'Appointment', 'AnyTime', 'Unknown'); + +-- AlterTable +ALTER TABLE "CourseSchedule" DROP CONSTRAINT "CourseSchedule_pkey", +DROP COLUMN "module", +ADD COLUMN "module" "Module" NOT NULL, +DROP COLUMN "day", +ADD COLUMN "day" "Day" NOT NULL, +ADD CONSTRAINT "CourseSchedule_pkey" PRIMARY KEY ("module", "day", "period", "room"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d4c3b1e..ef89641 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -19,6 +19,8 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + + courses Course[] } model Account { @@ -56,7 +58,7 @@ model VerificationToken { identifier String token String expires DateTime - + @@id([identifier, token]) } @@ -74,3 +76,52 @@ model Authenticator { @@id([userId, credentialID]) } + +model Course { + code String @unique @id + name String + credits Int + overview String + remarks String + type Int + recommendedGrade Int[] + schedules CourseSchedule[] + users User[] +} + +model CourseSchedule { + module Module + day Day + period Int + room String + courseCode String + course Course @relation(fields: [courseCode], references: [code]) + @@id([module, day, period, room]) +} + +enum Module { + SpringA + SpringB + SpringC + FallA + FallB + FallC + SummerVacation + SpringVacation + Annual + Unknown +} + +enum Day { + Sun + Mon + Tue + Wed + Thu + Fri + Sat + Intensive + Appointment + AnyTime + Unknown +} \ No newline at end of file diff --git a/src/app/adjust/client.tsx b/src/app/adjust/client.tsx index 4105f79..dcbf928 100644 --- a/src/app/adjust/client.tsx +++ b/src/app/adjust/client.tsx @@ -30,8 +30,9 @@ import ImportFileButton from "@/components/ImportFileButton" // { id: 6, name: "しゅんたろう", mail: "hiromichiosato@gmail.com" }, // ] -export default function SchedulePlanner(props: { isSignedIn: boolean; users: User[] }) { - const { isSignedIn, users } = props +export default function SchedulePlanner(props: { currentUserId: string|null; users: User[] }) { + const { currentUserId, users } = props + const isSignedIn = currentUserId != null const [title, setTitle] = useState("") const [selectedUserIds, setSelectedUserIds] = useState([]) const [selectedDurationMinute, setSelectedDurationMinute] = useState(60) @@ -62,7 +63,7 @@ export default function SchedulePlanner(props: { isSignedIn: boolean; users: Use {isSignedIn ? (
- +
) : ( "" diff --git a/src/app/adjust/page.tsx b/src/app/adjust/page.tsx index 81ce7cd..70469e8 100644 --- a/src/app/adjust/page.tsx +++ b/src/app/adjust/page.tsx @@ -7,5 +7,5 @@ export default async function AdjustPage() { const session = await getServerSession(authOptions) const users = (await db.allUsers()).filter(user => user.email !== session?.user.email) - return + return } diff --git a/src/components/ImportFileButton.tsx b/src/components/ImportFileButton.tsx index a5e86fc..d2e73c6 100644 --- a/src/components/ImportFileButton.tsx +++ b/src/components/ImportFileButton.tsx @@ -8,12 +8,15 @@ import { Button } from "./ui/button" import { Tooltip, TooltipProvider } from "./ui/tooltip" import { toast } from "react-toastify" import { TooltipContent, TooltipTrigger } from "@radix-ui/react-tooltip" +import { db, prisma } from "@/lib/prisma" +import { insertCoursesForUserOnFileLoad } from "@/lib/server" const parseRSReferToCodes = (content: string): string[] => content.split("\n").map(line => line.replaceAll(/["\s\r]/gi, "")) type Prop = { setCourses: Dispatch + currentUserId: string|null } /** @@ -43,6 +46,9 @@ export default function ImportFileButton(prop: Prop) { setUploadStatus("error") } else setUploadStatus("done") setCourses(yourCourses) + if (prop.currentUserId) { + insertCoursesForUserOnFileLoad(yourCourses, prop.currentUserId) + } }, [allCourses, contents, setCourses, file?.name]) useEffect(() => { diff --git a/src/lib/course.ts b/src/lib/course.ts index 9a7feb3..3674410 100644 --- a/src/lib/course.ts +++ b/src/lib/course.ts @@ -1,6 +1,8 @@ + import { setTimes } from "./utils" import { Period } from "./scheduling" import { Course, Day } from "@/third-party/twinte-parser-type" +import { db } from "./prisma" export type CoursePeriod = { course: Course @@ -116,3 +118,4 @@ export const courseToPeriods = (baseDate: Date, course: Course): Period[] => { // export const mixFreeClassPeriod = (freePeriods: Period[], classPeriod: Period[]) => { // } + diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index ead1582..4ad212c 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,4 +1,5 @@ -import { Account, PrismaClient, User } from "@prisma/client" +import { Account, PrismaClient, User, Course as PrismaCourse } from "@prisma/client" +import { Course } from "@/third-party/twinte-parser-type" const globalForPrisma = globalThis as unknown as { prisma: PrismaClient } export const prisma = globalForPrisma.prisma || new PrismaClient() @@ -16,4 +17,26 @@ export const db = { const users = await prisma.user.findMany() return users }, -} + + insertCoursesForUser: async (courses: Course[], userId: string) => { + const coursesConnected = courses.map(course => { + const { schedules, ...withoutSchedules} = course + return { + ...withoutSchedules, + schedules: { + connect: [ + {} + ] + }, + users: { + connect: [ { id: userId }] + } + } + }) + + const res = await prisma.course.createMany({ data: coursesConnected }) + console.log(res) + const resUpd = await prisma.course.updateMany({ data: courses }) + console.log(resUpd) + } +} \ No newline at end of file diff --git a/src/lib/server.ts b/src/lib/server.ts new file mode 100644 index 0000000..ed72c4f --- /dev/null +++ b/src/lib/server.ts @@ -0,0 +1,8 @@ +"use server" +import { Course, Day } from "@/third-party/twinte-parser-type" +import { db } from "./prisma" + + +export const insertCoursesForUserOnFileLoad = async (courses: Course[], userId: string) => { + const res = await db.insertCoursesForUser(courses, userId) +} \ No newline at end of file From de54a1417b4033adefc0e0587d61f879200d46a0 Mon Sep 17 00:00:00 2001 From: uxiun Date: Fri, 10 Jan 2025 14:22:55 +0900 Subject: [PATCH 2/9] database insert success, but still error --- .../migration.sql | 8 + .../migration.sql | 10 + prisma/schema.prisma | 3 + src/lib/prisma.ts | 193 +++++++++++++++++- src/lib/prismaTypes.ts | 1 + src/lib/server.ts | 5 +- src/third-party/twinte-parser.ts | 1 + 7 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 prisma/migrations/20250110021506_added_field_instructor_for_course/migration.sql create mode 100644 prisma/migrations/20250110022501_added_more_field_to_course/migration.sql create mode 100644 src/lib/prismaTypes.ts diff --git a/prisma/migrations/20250110021506_added_field_instructor_for_course/migration.sql b/prisma/migrations/20250110021506_added_field_instructor_for_course/migration.sql new file mode 100644 index 0000000..c9bb592 --- /dev/null +++ b/prisma/migrations/20250110021506_added_field_instructor_for_course/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `instructor` to the `Course` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Course" ADD COLUMN "instructor" TEXT NOT NULL; diff --git a/prisma/migrations/20250110022501_added_more_field_to_course/migration.sql b/prisma/migrations/20250110022501_added_more_field_to_course/migration.sql new file mode 100644 index 0000000..1108171 --- /dev/null +++ b/prisma/migrations/20250110022501_added_more_field_to_course/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - Added the required column `error` to the `Course` table without a default value. This is not possible if the table is not empty. + - Added the required column `lastUpdate` to the `Course` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Course" ADD COLUMN "error" BOOLEAN NOT NULL, +ADD COLUMN "lastUpdate" TIMESTAMP(3) NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ef89641..009fcc7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -87,6 +87,9 @@ model Course { recommendedGrade Int[] schedules CourseSchedule[] users User[] + instructor String + error Boolean + lastUpdate DateTime } model CourseSchedule { diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 4ad212c..a7ee7b0 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,10 +1,185 @@ -import { Account, PrismaClient, User, Course as PrismaCourse } from "@prisma/client" +import { + $Enums, + Account, + PrismaClient, + User, + PrismaPromise, +} from "@prisma/client" import { Course } from "@/third-party/twinte-parser-type" +import { Module, Day } from "@/third-party/twinte-parser-type" const globalForPrisma = globalThis as unknown as { prisma: PrismaClient } export const prisma = globalForPrisma.prisma || new PrismaClient() if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma +export const moduleAsPrismaEnum = (module: Module): $Enums.Module => + module == Module.SpringA + ? "SpringA" + : module == Module.SpringB + ? "SpringB" + : module == Module.SpringC + ? "SpringC" + : module == Module.FallA + ? "FallA" + : module == Module.FallB + ? "FallB" + : module == Module.FallC + ? "FallC" + : module == Module.SummerVacation + ? "SummerVacation" + : module == Module.SpringVacation + ? "SpringVacation" + : module == Module.Annual + ? "Annual" + : "Unknown" + +export const dayAsPrismaEnum = (day: Day): $Enums.Day => + day == Day.Sun + ? "Sun" + : day == Day.Mon + ? "Mon" + : day == Day.Tue + ? "Tue" + : day == Day.Wed + ? "Wed" + : day == Day.Thu + ? "Thu" + : day == Day.Fri + ? "Fri" + : day == Day.Sat + ? "Sat" + : day == Day.Intensive + ? "Intensive" + : day == Day.Appointment + ? "Appointment" + : day == Day.AnyTime + ? "AnyTime" + : "Unknown" + +const scheduleAsPrisma = (schedule: { + module: Module + day: Day + period: number + room: string +}) => ({ + ...schedule, + module: moduleAsPrismaEnum(schedule.module), + day: dayAsPrismaEnum(schedule.day), +}) + +// const upsertSchedulesOfCourses = async (courses: Course[]) => { +// return prisma.$transaction(courses.flatMap(course => { + +// return course.schedules +// .map(scheduleAsPrisma) +// .map(schedule => prisma.courseSchedule.upsert({ +// where: { +// module_day_period_room: schedule +// }, +// create: { +// ...schedule, +// courseCode: course.code +// }, +// update: schedule, +// })) + +// })) +// } + +const upsertCourseConnectUser = async (course: Course, userId: string) => { + const existingCourse = await prisma.course.findUnique({ + where: { code: course.code }, + }) + + const update = { + name: course.name, + credits: course.credits, + overview: course.overview, + remarks: course.remarks, + type: course.type, + recommendedGrade: course.recommendedGrade, + instructor: course.instructor, + error: course.error, + lastUpdate: course.lastUpdate, + users: { + connect: [{ id: userId }], + }, + } + + // const schedules = { + // connectOrCreate: course.schedules.map(s => ({ + // module_day_period_room: scheduleAsPrisma(s) + // })) + // } + + const where = { code: course.code } + + const upsertSchedules = () => { + const schedulePromises = course.schedules.map(scheduleAsPrisma).map(schedule => { + return prisma.courseSchedule.upsert({ + where: { + module_day_period_room: schedule, + }, + update: { + courseCode: course.code, + }, + create: { + ...schedule, + courseCode: course.code, + }, + }) + }) + + return prisma.$transaction(schedulePromises) + } + + if (existingCourse && new Date(course.lastUpdate) > new Date(existingCourse.lastUpdate)) { + return prisma.course + .update({ + where, + data: { + ...update, + }, + }) + .then(upsertSchedules) + } else { + return prisma.course + .upsert({ + where, + update: { + ...update, + }, + create: { + ...update, + ...where, + }, + }) + .then(upsertSchedules) + } +} + +const upsertCourses = async (courses: Course[], userId: string) => { + const upsertPromises = courses.map(async course => upsertCourseConnectUser(course, userId)) + + return await prisma.$transaction(upsertPromises as PrismaPromise[]) +} + +export namespace Db { + export const resetUserCourses = async (userId: string) => { + return await prisma.user.update({ + where: { id: userId }, + data: { courses: { set: [] } }, + }) + } + + export const setCoursesForUser = async (courses: Course[], userId: string) => { + const r = await resetUserCourses(userId) + console.log("resetUserCourses:", r) + const s = await upsertCourses(courses, userId) + console.log("upsertCourses:", s) + } +} + export const db = { findAccount: async (userId: string): Promise => { const account = await prisma.account.findFirst({ @@ -20,17 +195,15 @@ export const db = { insertCoursesForUser: async (courses: Course[], userId: string) => { const coursesConnected = courses.map(course => { - const { schedules, ...withoutSchedules} = course + const { schedules, ...withoutSchedules } = course return { - ...withoutSchedules, + ...withoutSchedules, schedules: { - connect: [ - {} - ] + connect: [{}], }, users: { - connect: [ { id: userId }] - } + connect: [{ id: userId }], + }, } }) @@ -38,5 +211,5 @@ export const db = { console.log(res) const resUpd = await prisma.course.updateMany({ data: courses }) console.log(resUpd) - } -} \ No newline at end of file + }, +} diff --git a/src/lib/prismaTypes.ts b/src/lib/prismaTypes.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/lib/prismaTypes.ts @@ -0,0 +1 @@ + diff --git a/src/lib/server.ts b/src/lib/server.ts index ed72c4f..b4d2140 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -1,8 +1,9 @@ "use server" import { Course, Day } from "@/third-party/twinte-parser-type" -import { db } from "./prisma" +import { Db, db } from "./prisma" export const insertCoursesForUserOnFileLoad = async (courses: Course[], userId: string) => { - const res = await db.insertCoursesForUser(courses, userId) + // const res = await db.insertCoursesForUser(courses, userId) + await Db.setCoursesForUser(courses, userId) } \ No newline at end of file diff --git a/src/third-party/twinte-parser.ts b/src/third-party/twinte-parser.ts index 82a533f..3170069 100644 --- a/src/third-party/twinte-parser.ts +++ b/src/third-party/twinte-parser.ts @@ -5,6 +5,7 @@ import fs from "fs" import path from "path" +import { Day, Module } from "./twinte-parser-type" export const fetchCourses = () => { const filePath = path.join(process.cwd(), "public", "kdb.json") From 77afe921b59a77eacb3ddc0703273e71dffa2b8b Mon Sep 17 00:00:00 2001 From: uxiun Date: Fri, 10 Jan 2025 14:29:16 +0900 Subject: [PATCH 3/9] lint --- src/app/adjust/client.tsx | 2 +- src/app/adjust/page.tsx | 2 +- src/components/ImportFileButton.tsx | 3 +-- src/lib/course.ts | 3 --- src/lib/prisma.ts | 35 ++++++++++++----------------- src/lib/prismaTypes.ts | 1 - src/lib/server.ts | 10 ++++----- src/third-party/twinte-parser.ts | 1 - 8 files changed, 21 insertions(+), 36 deletions(-) delete mode 100644 src/lib/prismaTypes.ts diff --git a/src/app/adjust/client.tsx b/src/app/adjust/client.tsx index dcbf928..5703781 100644 --- a/src/app/adjust/client.tsx +++ b/src/app/adjust/client.tsx @@ -30,7 +30,7 @@ import ImportFileButton from "@/components/ImportFileButton" // { id: 6, name: "しゅんたろう", mail: "hiromichiosato@gmail.com" }, // ] -export default function SchedulePlanner(props: { currentUserId: string|null; users: User[] }) { +export default function SchedulePlanner(props: { currentUserId: string | null; users: User[] }) { const { currentUserId, users } = props const isSignedIn = currentUserId != null const [title, setTitle] = useState("") diff --git a/src/app/adjust/page.tsx b/src/app/adjust/page.tsx index 70469e8..ac3ee5c 100644 --- a/src/app/adjust/page.tsx +++ b/src/app/adjust/page.tsx @@ -7,5 +7,5 @@ export default async function AdjustPage() { const session = await getServerSession(authOptions) const users = (await db.allUsers()).filter(user => user.email !== session?.user.email) - return + return } diff --git a/src/components/ImportFileButton.tsx b/src/components/ImportFileButton.tsx index d2e73c6..5ea5291 100644 --- a/src/components/ImportFileButton.tsx +++ b/src/components/ImportFileButton.tsx @@ -8,7 +8,6 @@ import { Button } from "./ui/button" import { Tooltip, TooltipProvider } from "./ui/tooltip" import { toast } from "react-toastify" import { TooltipContent, TooltipTrigger } from "@radix-ui/react-tooltip" -import { db, prisma } from "@/lib/prisma" import { insertCoursesForUserOnFileLoad } from "@/lib/server" const parseRSReferToCodes = (content: string): string[] => @@ -16,7 +15,7 @@ const parseRSReferToCodes = (content: string): string[] => type Prop = { setCourses: Dispatch - currentUserId: string|null + currentUserId: string | null } /** diff --git a/src/lib/course.ts b/src/lib/course.ts index 3674410..9a7feb3 100644 --- a/src/lib/course.ts +++ b/src/lib/course.ts @@ -1,8 +1,6 @@ - import { setTimes } from "./utils" import { Period } from "./scheduling" import { Course, Day } from "@/third-party/twinte-parser-type" -import { db } from "./prisma" export type CoursePeriod = { course: Course @@ -118,4 +116,3 @@ export const courseToPeriods = (baseDate: Date, course: Course): Period[] => { // export const mixFreeClassPeriod = (freePeriods: Period[], classPeriod: Period[]) => { // } - diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index a7ee7b0..aac88cb 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,10 +1,4 @@ -import { - $Enums, - Account, - PrismaClient, - User, - PrismaPromise, -} from "@prisma/client" +import { $Enums, Account, PrismaClient, User, PrismaPromise } from "@prisma/client" import { Course } from "@/third-party/twinte-parser-type" import { Module, Day } from "@/third-party/twinte-parser-type" @@ -160,24 +154,22 @@ const upsertCourseConnectUser = async (course: Course, userId: string) => { const upsertCourses = async (courses: Course[], userId: string) => { const upsertPromises = courses.map(async course => upsertCourseConnectUser(course, userId)) - + // eslint-disable-next-line @typescript-eslint/no-explicit-any return await prisma.$transaction(upsertPromises as PrismaPromise[]) } -export namespace Db { - export const resetUserCourses = async (userId: string) => { - return await prisma.user.update({ - where: { id: userId }, - data: { courses: { set: [] } }, - }) - } +export const resetUserCourses = async (userId: string) => { + return await prisma.user.update({ + where: { id: userId }, + data: { courses: { set: [] } }, + }) +} - export const setCoursesForUser = async (courses: Course[], userId: string) => { - const r = await resetUserCourses(userId) - console.log("resetUserCourses:", r) - const s = await upsertCourses(courses, userId) - console.log("upsertCourses:", s) - } +export const setCoursesForUser = async (courses: Course[], userId: string) => { + const r = await resetUserCourses(userId) + console.log("resetUserCourses:", r) + const s = await upsertCourses(courses, userId) + console.log("upsertCourses:", s) } export const db = { @@ -195,6 +187,7 @@ export const db = { insertCoursesForUser: async (courses: Course[], userId: string) => { const coursesConnected = courses.map(course => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { schedules, ...withoutSchedules } = course return { ...withoutSchedules, diff --git a/src/lib/prismaTypes.ts b/src/lib/prismaTypes.ts deleted file mode 100644 index 8b13789..0000000 --- a/src/lib/prismaTypes.ts +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib/server.ts b/src/lib/server.ts index b4d2140..adf7589 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -1,9 +1,7 @@ "use server" -import { Course, Day } from "@/third-party/twinte-parser-type" -import { Db, db } from "./prisma" - - +import { Course } from "@/third-party/twinte-parser-type" +import { setCoursesForUser } from "./prisma" export const insertCoursesForUserOnFileLoad = async (courses: Course[], userId: string) => { // const res = await db.insertCoursesForUser(courses, userId) - await Db.setCoursesForUser(courses, userId) -} \ No newline at end of file + await setCoursesForUser(courses, userId) +} diff --git a/src/third-party/twinte-parser.ts b/src/third-party/twinte-parser.ts index 3170069..82a533f 100644 --- a/src/third-party/twinte-parser.ts +++ b/src/third-party/twinte-parser.ts @@ -5,7 +5,6 @@ import fs from "fs" import path from "path" -import { Day, Module } from "./twinte-parser-type" export const fetchCourses = () => { const filePath = path.join(process.cwd(), "public", "kdb.json") From dda1104c4d7e084aff52e705c4f16f2362eb7576 Mon Sep 17 00:00:00 2001 From: uxiun Date: Thu, 16 Jan 2025 15:47:44 +0900 Subject: [PATCH 4/9] simplify schema.prisma --- prisma/schema.prisma | 74 ++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 47 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 009fcc7..0d06bd7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -78,53 +78,33 @@ model Authenticator { } model Course { - code String @unique @id - name String - credits Int - overview String - remarks String - type Int - recommendedGrade Int[] - schedules CourseSchedule[] + code String @id @unique users User[] - instructor String - error Boolean - lastUpdate DateTime } -model CourseSchedule { - module Module - day Day - period Int - room String - courseCode String - course Course @relation(fields: [courseCode], references: [code]) - @@id([module, day, period, room]) -} - -enum Module { - SpringA - SpringB - SpringC - FallA - FallB - FallC - SummerVacation - SpringVacation - Annual - Unknown -} - -enum Day { - Sun - Mon - Tue - Wed - Thu - Fri - Sat - Intensive - Appointment - AnyTime - Unknown -} \ No newline at end of file +// enum Module { +// SpringA +// SpringB +// SpringC +// FallA +// FallB +// FallC +// SummerVacation +// SpringVacation +// Annual +// Unknown +// } + +// enum Day { +// Sun +// Mon +// Tue +// Wed +// Thu +// Fri +// Sat +// Intensive +// Appointment +// AnyTime +// Unknown +// } \ No newline at end of file From b1c3a99197ea37aec6f06031c5cb211002ae561c Mon Sep 17 00:00:00 2001 From: uxiun Date: Thu, 16 Jan 2025 17:00:07 +0900 Subject: [PATCH 5/9] save --- .../migration.sql | 37 +++ src/app/adjust/client.tsx | 18 +- src/app/adjust/page.tsx | 19 +- src/components/ImportFileButton.tsx | 16 +- src/lib/prisma.ts | 211 +++++++----------- src/lib/server.ts | 8 +- 6 files changed, 160 insertions(+), 149 deletions(-) create mode 100644 prisma/migrations/20250116064926_simple_course_user_relation/migration.sql diff --git a/prisma/migrations/20250116064926_simple_course_user_relation/migration.sql b/prisma/migrations/20250116064926_simple_course_user_relation/migration.sql new file mode 100644 index 0000000..2e69b80 --- /dev/null +++ b/prisma/migrations/20250116064926_simple_course_user_relation/migration.sql @@ -0,0 +1,37 @@ +/* + Warnings: + + - You are about to drop the column `credits` on the `Course` table. All the data in the column will be lost. + - You are about to drop the column `error` on the `Course` table. All the data in the column will be lost. + - You are about to drop the column `instructor` on the `Course` table. All the data in the column will be lost. + - You are about to drop the column `lastUpdate` on the `Course` table. All the data in the column will be lost. + - You are about to drop the column `name` on the `Course` table. All the data in the column will be lost. + - You are about to drop the column `overview` on the `Course` table. All the data in the column will be lost. + - You are about to drop the column `recommendedGrade` on the `Course` table. All the data in the column will be lost. + - You are about to drop the column `remarks` on the `Course` table. All the data in the column will be lost. + - You are about to drop the column `type` on the `Course` table. All the data in the column will be lost. + - You are about to drop the `CourseSchedule` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "CourseSchedule" DROP CONSTRAINT "CourseSchedule_courseCode_fkey"; + +-- AlterTable +ALTER TABLE "Course" DROP COLUMN "credits", +DROP COLUMN "error", +DROP COLUMN "instructor", +DROP COLUMN "lastUpdate", +DROP COLUMN "name", +DROP COLUMN "overview", +DROP COLUMN "recommendedGrade", +DROP COLUMN "remarks", +DROP COLUMN "type"; + +-- DropTable +DROP TABLE "CourseSchedule"; + +-- DropEnum +DROP TYPE "Day"; + +-- DropEnum +DROP TYPE "Module"; diff --git a/src/app/adjust/client.tsx b/src/app/adjust/client.tsx index 5703781..786fb8d 100644 --- a/src/app/adjust/client.tsx +++ b/src/app/adjust/client.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { Dispatch, useState } from "react" import { Input } from "@/components/ui/input" import { Checkbox } from "@/components/ui/checkbox" import { Label } from "@/components/ui/label" @@ -30,14 +30,20 @@ import ImportFileButton from "@/components/ImportFileButton" // { id: 6, name: "しゅんたろう", mail: "hiromichiosato@gmail.com" }, // ] -export default function SchedulePlanner(props: { currentUserId: string | null; users: User[] }) { +export default function SchedulePlanner(props: { + currentUserId: string | null + users: User[] + allCourses?: Course[] + setAllCourses?: Dispatch + courses?: Course[] +}) { const { currentUserId, users } = props const isSignedIn = currentUserId != null const [title, setTitle] = useState("") const [selectedUserIds, setSelectedUserIds] = useState([]) const [selectedDurationMinute, setSelectedDurationMinute] = useState(60) const [excludePeriod, setExcludePeriod] = useState({ start: 22, end: 8 }) - const [courses, setCourses] = useState([]) + const [courses, setCourses] = useState(props.courses ?? []) return (
@@ -63,7 +69,11 @@ export default function SchedulePlanner(props: { currentUserId: string | null; u {isSignedIn ? (
- +
) : ( "" diff --git a/src/app/adjust/page.tsx b/src/app/adjust/page.tsx index ac3ee5c..479d18b 100644 --- a/src/app/adjust/page.tsx +++ b/src/app/adjust/page.tsx @@ -1,11 +1,26 @@ -import { db } from "@/lib/prisma" +import { db, getUserCourseIds } from "@/lib/prisma" import SchedulePlanner from "@/app/adjust/client" import { getServerSession } from "next-auth" import { authOptions } from "@/lib/auth" +import { fetchCourses } from "@/third-party/twinte-parser" +import { Course } from "@/third-party/twinte-parser-type" export default async function AdjustPage() { const session = await getServerSession(authOptions) const users = (await db.allUsers()).filter(user => user.email !== session?.user.email) + const allCourses = await fetchCourses() as Course[] + // console.log(allCourses) - return + const userCourseIds = (session?.user.id ? + await getUserCourseIds(session.user.id) : undefined) + console.log("userCourseIds:", userCourseIds) + + const courses = allCourses.filter(course => userCourseIds?.includes(course.code)) + + return } diff --git a/src/components/ImportFileButton.tsx b/src/components/ImportFileButton.tsx index 5ea5291..f4e8fec 100644 --- a/src/components/ImportFileButton.tsx +++ b/src/components/ImportFileButton.tsx @@ -16,6 +16,8 @@ const parseRSReferToCodes = (content: string): string[] => type Prop = { setCourses: Dispatch currentUserId: string | null + allCourses?: Course[] + setAllCourses?: Dispatch } /** @@ -26,7 +28,7 @@ type Prop = { export default function ImportFileButton(prop: Prop) { const { setCourses } = prop const [contents, setContents] = useState() - const [allCourses, setAllCourses] = useState([]) + // const [allCourses, setAllCourses] = useState([]) const fileInputRef = useRef(null) const [uploadStatus, setUploadStatus] = useState<"done" | "yet" | "error">("yet") @@ -36,7 +38,7 @@ export default function ImportFileButton(prop: Prop) { if (!contents || !file?.name) return const codes = parseRSReferToCodes(contents) console.log("codes", codes) - const yourCourses = allCourses.filter(c => codes.includes(c.code)) + const yourCourses = prop.allCourses.filter(c => codes.includes(c.code)) if (yourCourses.length == 0) { toast( `科目が見つかりませんでした。 ${file.name} の形式が間違っているかもしれません。\nTWINS から履修情報を出力した RSReferCSV.csv に類するファイルであることをご確認ください。`, @@ -48,17 +50,17 @@ export default function ImportFileButton(prop: Prop) { if (prop.currentUserId) { insertCoursesForUserOnFileLoad(yourCourses, prop.currentUserId) } - }, [allCourses, contents, setCourses, file?.name]) + }, [prop.allCourses, contents, setCourses, file?.name]) useEffect(() => { searchCourses() - }, [contents, allCourses, searchCourses]) + }, [contents, prop.allCourses, searchCourses]) const handleChange = async (e: React.ChangeEvent) => { - if (allCourses.length == 0) { + if (prop.allCourses.length == 0) { try { - const all = (await fetchCourses()) as Course[] - if (!contents) setAllCourses(all) + const all = fetchCourses() as Course[] + if (!contents) prop.setAllCourses(all) } catch (e) { console.error(e) toast(`KdB JSON を Course[] として解析できませんでした。`, { type: "error" }) diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index aac88cb..eb7dbd7 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,4 +1,6 @@ -import { $Enums, Account, PrismaClient, User, PrismaPromise } from "@prisma/client" +import { + // $Enums, + Account, PrismaClient, User, PrismaPromise } from "@prisma/client" import { Course } from "@/third-party/twinte-parser-type" import { Module, Day } from "@/third-party/twinte-parser-type" @@ -6,150 +8,79 @@ const globalForPrisma = globalThis as unknown as { prisma: PrismaClient } export const prisma = globalForPrisma.prisma || new PrismaClient() if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma -export const moduleAsPrismaEnum = (module: Module): $Enums.Module => - module == Module.SpringA - ? "SpringA" - : module == Module.SpringB - ? "SpringB" - : module == Module.SpringC - ? "SpringC" - : module == Module.FallA - ? "FallA" - : module == Module.FallB - ? "FallB" - : module == Module.FallC - ? "FallC" - : module == Module.SummerVacation - ? "SummerVacation" - : module == Module.SpringVacation - ? "SpringVacation" - : module == Module.Annual - ? "Annual" - : "Unknown" - -export const dayAsPrismaEnum = (day: Day): $Enums.Day => - day == Day.Sun - ? "Sun" - : day == Day.Mon - ? "Mon" - : day == Day.Tue - ? "Tue" - : day == Day.Wed - ? "Wed" - : day == Day.Thu - ? "Thu" - : day == Day.Fri - ? "Fri" - : day == Day.Sat - ? "Sat" - : day == Day.Intensive - ? "Intensive" - : day == Day.Appointment - ? "Appointment" - : day == Day.AnyTime - ? "AnyTime" - : "Unknown" - -const scheduleAsPrisma = (schedule: { - module: Module - day: Day - period: number - room: string -}) => ({ - ...schedule, - module: moduleAsPrismaEnum(schedule.module), - day: dayAsPrismaEnum(schedule.day), -}) - -// const upsertSchedulesOfCourses = async (courses: Course[]) => { -// return prisma.$transaction(courses.flatMap(course => { - -// return course.schedules -// .map(scheduleAsPrisma) -// .map(schedule => prisma.courseSchedule.upsert({ -// where: { -// module_day_period_room: schedule -// }, -// create: { -// ...schedule, -// courseCode: course.code -// }, -// update: schedule, -// })) - -// })) -// } +// export const moduleAsPrismaEnum = (module: Module): $Enums.Module => +// module == Module.SpringA +// ? "SpringA" +// : module == Module.SpringB +// ? "SpringB" +// : module == Module.SpringC +// ? "SpringC" +// : module == Module.FallA +// ? "FallA" +// : module == Module.FallB +// ? "FallB" +// : module == Module.FallC +// ? "FallC" +// : module == Module.SummerVacation +// ? "SummerVacation" +// : module == Module.SpringVacation +// ? "SpringVacation" +// : module == Module.Annual +// ? "Annual" +// : "Unknown" + +// export const dayAsPrismaEnum = (day: Day): $Enums.Day => +// day == Day.Sun +// ? "Sun" +// : day == Day.Mon +// ? "Mon" +// : day == Day.Tue +// ? "Tue" +// : day == Day.Wed +// ? "Wed" +// : day == Day.Thu +// ? "Thu" +// : day == Day.Fri +// ? "Fri" +// : day == Day.Sat +// ? "Sat" +// : day == Day.Intensive +// ? "Intensive" +// : day == Day.Appointment +// ? "Appointment" +// : day == Day.AnyTime +// ? "AnyTime" +// : "Unknown" + +// const scheduleAsPrisma = (schedule: { +// module: Module +// day: Day +// period: number +// room: string +// }) => ({ +// ...schedule, +// module: moduleAsPrismaEnum(schedule.module), +// day: dayAsPrismaEnum(schedule.day), +// }) const upsertCourseConnectUser = async (course: Course, userId: string) => { - const existingCourse = await prisma.course.findUnique({ - where: { code: course.code }, - }) + // const existingCourse = await prisma.course.findUnique({ + // where: { code: course.code }, + // }) const update = { - name: course.name, - credits: course.credits, - overview: course.overview, - remarks: course.remarks, - type: course.type, - recommendedGrade: course.recommendedGrade, - instructor: course.instructor, - error: course.error, - lastUpdate: course.lastUpdate, users: { connect: [{ id: userId }], }, } - // const schedules = { - // connectOrCreate: course.schedules.map(s => ({ - // module_day_period_room: scheduleAsPrisma(s) - // })) - // } - - const where = { code: course.code } - - const upsertSchedules = () => { - const schedulePromises = course.schedules.map(scheduleAsPrisma).map(schedule => { - return prisma.courseSchedule.upsert({ - where: { - module_day_period_room: schedule, - }, - update: { - courseCode: course.code, - }, - create: { - ...schedule, - courseCode: course.code, - }, - }) + return prisma.course + .update({ + where: { code: course.code }, + data: { + ...update, + }, }) - - return prisma.$transaction(schedulePromises) - } - - if (existingCourse && new Date(course.lastUpdate) > new Date(existingCourse.lastUpdate)) { - return prisma.course - .update({ - where, - data: { - ...update, - }, - }) - .then(upsertSchedules) - } else { - return prisma.course - .upsert({ - where, - update: { - ...update, - }, - create: { - ...update, - ...where, - }, - }) - .then(upsertSchedules) - } } const upsertCourses = async (courses: Course[], userId: string) => { @@ -205,4 +136,14 @@ export const db = { const resUpd = await prisma.course.updateMany({ data: courses }) console.log(resUpd) }, + +} + +export const getUserCourseIds = async (userId: string): Promise => { + const userWithCourses = await prisma.user.findUnique({ + where: { id: userId }, + include: { courses: true } + }) + + return userWithCourses?.courses.map(({ code }) => code) } diff --git a/src/lib/server.ts b/src/lib/server.ts index adf7589..d054f44 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -1,7 +1,13 @@ "use server" import { Course } from "@/third-party/twinte-parser-type" -import { setCoursesForUser } from "./prisma" +import { getUserCourseIds, prisma, setCoursesForUser } from "./prisma" export const insertCoursesForUserOnFileLoad = async (courses: Course[], userId: string) => { // const res = await db.insertCoursesForUser(courses, userId) await setCoursesForUser(courses, userId) } + +export const getUserCourses = async (userId: string): Promise => { + const courseIds = await getUserCourseIds(userId) + if (courseIds == undefined) return undefined + +} From 305f6db9006fe3306c57cf11b73f93fade394226 Mon Sep 17 00:00:00 2001 From: uxiun Date: Fri, 17 Jan 2025 12:47:32 +0900 Subject: [PATCH 6/9] save --- src/app/adjust/client.tsx | 6 +++--- src/components/ImportFileButton.tsx | 9 ++++++--- src/lib/prisma.ts | 5 ++++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/app/adjust/client.tsx b/src/app/adjust/client.tsx index 786fb8d..f4f80be 100644 --- a/src/app/adjust/client.tsx +++ b/src/app/adjust/client.tsx @@ -1,6 +1,6 @@ "use client" -import { Dispatch, useState } from "react" +import { useState } from "react" import { Input } from "@/components/ui/input" import { Checkbox } from "@/components/ui/checkbox" import { Label } from "@/components/ui/label" @@ -34,7 +34,6 @@ export default function SchedulePlanner(props: { currentUserId: string | null users: User[] allCourses?: Course[] - setAllCourses?: Dispatch courses?: Course[] }) { const { currentUserId, users } = props @@ -44,6 +43,7 @@ export default function SchedulePlanner(props: { const [selectedDurationMinute, setSelectedDurationMinute] = useState(60) const [excludePeriod, setExcludePeriod] = useState({ start: 22, end: 8 }) const [courses, setCourses] = useState(props.courses ?? []) + const [allCourses, setAllCourses] = useState(props.allCourses ?? []) return (
@@ -72,7 +72,7 @@ export default function SchedulePlanner(props: {
) : ( diff --git a/src/components/ImportFileButton.tsx b/src/components/ImportFileButton.tsx index f4e8fec..14e84a4 100644 --- a/src/components/ImportFileButton.tsx +++ b/src/components/ImportFileButton.tsx @@ -17,7 +17,7 @@ type Prop = { setCourses: Dispatch currentUserId: string | null allCourses?: Course[] - setAllCourses?: Dispatch + setAllCourses: Dispatch } /** @@ -31,6 +31,8 @@ export default function ImportFileButton(prop: Prop) { // const [allCourses, setAllCourses] = useState([]) const fileInputRef = useRef(null) const [uploadStatus, setUploadStatus] = useState<"done" | "yet" | "error">("yet") + const isCoursesRegistered = prop.allCourses !== undefined + const [file, setFile] = useState(null) @@ -38,7 +40,8 @@ export default function ImportFileButton(prop: Prop) { if (!contents || !file?.name) return const codes = parseRSReferToCodes(contents) console.log("codes", codes) - const yourCourses = prop.allCourses.filter(c => codes.includes(c.code)) + + const yourCourses = prop.allCourses?.filter(c => codes.includes(c.code)) ?? [] if (yourCourses.length == 0) { toast( `科目が見つかりませんでした。 ${file.name} の形式が間違っているかもしれません。\nTWINS から履修情報を出力した RSReferCSV.csv に類するファイルであることをご確認ください。`, @@ -57,7 +60,7 @@ export default function ImportFileButton(prop: Prop) { }, [contents, prop.allCourses, searchCourses]) const handleChange = async (e: React.ChangeEvent) => { - if (prop.allCourses.length == 0) { + if (prop.allCourses?.length == 0) { try { const all = fetchCourses() as Course[] if (!contents) prop.setAllCourses(all) diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index eb7dbd7..bb3c6cb 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -69,6 +69,7 @@ const upsertCourseConnectUser = async (course: Course, userId: string) => { // }) const update = { + users: { connect: [{ id: userId }], }, @@ -78,7 +79,9 @@ const upsertCourseConnectUser = async (course: Course, userId: string) => { .update({ where: { code: course.code }, data: { - ...update, + users: { + connect: [{ id: userId }] + }, }, }) } From 85fd996ea799b945d24171a8af2fe041d36f282b Mon Sep 17 00:00:00 2001 From: inucat <70513648+inucat@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:46:55 +0900 Subject: [PATCH 7/9] =?UTF-8?q?=E5=B1=A5=E4=BF=AE=E3=82=B9=E3=82=AD?= =?UTF-8?q?=E3=83=BC=E3=83=9E=E3=81=AE=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/schema.prisma | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0d06bd7..2d7e461 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -78,33 +78,9 @@ model Authenticator { } model Course { - code String @id @unique - users User[] -} + userId String + code String + user User @relation(fields: [userId], references: [id]) -// enum Module { -// SpringA -// SpringB -// SpringC -// FallA -// FallB -// FallC -// SummerVacation -// SpringVacation -// Annual -// Unknown -// } - -// enum Day { -// Sun -// Mon -// Tue -// Wed -// Thu -// Fri -// Sat -// Intensive -// Appointment -// AnyTime -// Unknown -// } \ No newline at end of file + @@id([userId, code]) +} From f08a626d706b3d80864626477f5eeaefbf4ff8b8 Mon Sep 17 00:00:00 2001 From: uxiun Date: Fri, 17 Jan 2025 20:22:44 +0900 Subject: [PATCH 8/9] =?UTF-8?q?CSV=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=81=BF?= =?UTF-8?q?=E6=99=82=E3=81=AEDB=E6=9B=B4=E6=96=B0=E3=81=A8=E3=80=81?= =?UTF-8?q?=E4=BA=8B=E5=89=8D=E3=81=AE=E6=8E=88=E6=A5=AD=E5=8F=96=E5=BE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/20250117110807_/migration.sql | 27 ++++ src/app/adjust/client.tsx | 5 +- src/app/adjust/page.tsx | 22 +-- src/components/ImportFileButton.tsx | 6 +- src/lib/prisma.ts | 136 +----------------- src/lib/server.ts | 49 ++++++- 6 files changed, 90 insertions(+), 155 deletions(-) create mode 100644 prisma/migrations/20250117110807_/migration.sql diff --git a/prisma/migrations/20250117110807_/migration.sql b/prisma/migrations/20250117110807_/migration.sql new file mode 100644 index 0000000..ba2b49a --- /dev/null +++ b/prisma/migrations/20250117110807_/migration.sql @@ -0,0 +1,27 @@ +/* + Warnings: + + - The primary key for the `Course` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the `_CourseToUser` table. If the table is not empty, all the data it contains will be lost. + - Added the required column `userId` to the `Course` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "_CourseToUser" DROP CONSTRAINT "_CourseToUser_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_CourseToUser" DROP CONSTRAINT "_CourseToUser_B_fkey"; + +-- DropIndex +DROP INDEX "Course_code_key"; + +-- AlterTable +ALTER TABLE "Course" DROP CONSTRAINT "Course_pkey", +ADD COLUMN "userId" TEXT NOT NULL, +ADD CONSTRAINT "Course_pkey" PRIMARY KEY ("userId", "code"); + +-- DropTable +DROP TABLE "_CourseToUser"; + +-- AddForeignKey +ALTER TABLE "Course" ADD CONSTRAINT "Course_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/src/app/adjust/client.tsx b/src/app/adjust/client.tsx index f4f80be..73264e6 100644 --- a/src/app/adjust/client.tsx +++ b/src/app/adjust/client.tsx @@ -71,9 +71,10 @@ export default function SchedulePlanner(props: {
+ currentUserId={currentUserId} + />
) : ( "" diff --git a/src/app/adjust/page.tsx b/src/app/adjust/page.tsx index 479d18b..f7be30b 100644 --- a/src/app/adjust/page.tsx +++ b/src/app/adjust/page.tsx @@ -1,26 +1,28 @@ -import { db, getUserCourseIds } from "@/lib/prisma" +import { db } from "@/lib/prisma" import SchedulePlanner from "@/app/adjust/client" import { getServerSession } from "next-auth" import { authOptions } from "@/lib/auth" import { fetchCourses } from "@/third-party/twinte-parser" import { Course } from "@/third-party/twinte-parser-type" +import { getUserCourseIds } from "@/lib/server" export default async function AdjustPage() { const session = await getServerSession(authOptions) const users = (await db.allUsers()).filter(user => user.email !== session?.user.email) - const allCourses = await fetchCourses() as Course[] + const allCourses = (await fetchCourses()) as Course[] // console.log(allCourses) - const userCourseIds = (session?.user.id ? - await getUserCourseIds(session.user.id) : undefined) + const userCourseIds = session?.user.id ? await getUserCourseIds(session.user.id) : undefined console.log("userCourseIds:", userCourseIds) const courses = allCourses.filter(course => userCourseIds?.includes(course.code)) - return + return ( + + ) } diff --git a/src/components/ImportFileButton.tsx b/src/components/ImportFileButton.tsx index 14e84a4..31bff71 100644 --- a/src/components/ImportFileButton.tsx +++ b/src/components/ImportFileButton.tsx @@ -8,7 +8,7 @@ import { Button } from "./ui/button" import { Tooltip, TooltipProvider } from "./ui/tooltip" import { toast } from "react-toastify" import { TooltipContent, TooltipTrigger } from "@radix-ui/react-tooltip" -import { insertCoursesForUserOnFileLoad } from "@/lib/server" +import { resetAndUpdateUserCourses } from "@/lib/server" const parseRSReferToCodes = (content: string): string[] => content.split("\n").map(line => line.replaceAll(/["\s\r]/gi, "")) @@ -31,8 +31,6 @@ export default function ImportFileButton(prop: Prop) { // const [allCourses, setAllCourses] = useState([]) const fileInputRef = useRef(null) const [uploadStatus, setUploadStatus] = useState<"done" | "yet" | "error">("yet") - const isCoursesRegistered = prop.allCourses !== undefined - const [file, setFile] = useState(null) @@ -51,7 +49,7 @@ export default function ImportFileButton(prop: Prop) { } else setUploadStatus("done") setCourses(yourCourses) if (prop.currentUserId) { - insertCoursesForUserOnFileLoad(yourCourses, prop.currentUserId) + resetAndUpdateUserCourses(prop.currentUserId, yourCourses) } }, [prop.allCourses, contents, setCourses, file?.name]) diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index bb3c6cb..d0527f4 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,111 +1,14 @@ import { // $Enums, - Account, PrismaClient, User, PrismaPromise } from "@prisma/client" -import { Course } from "@/third-party/twinte-parser-type" -import { Module, Day } from "@/third-party/twinte-parser-type" + Account, + PrismaClient, + User, +} from "@prisma/client" const globalForPrisma = globalThis as unknown as { prisma: PrismaClient } export const prisma = globalForPrisma.prisma || new PrismaClient() if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma -// export const moduleAsPrismaEnum = (module: Module): $Enums.Module => -// module == Module.SpringA -// ? "SpringA" -// : module == Module.SpringB -// ? "SpringB" -// : module == Module.SpringC -// ? "SpringC" -// : module == Module.FallA -// ? "FallA" -// : module == Module.FallB -// ? "FallB" -// : module == Module.FallC -// ? "FallC" -// : module == Module.SummerVacation -// ? "SummerVacation" -// : module == Module.SpringVacation -// ? "SpringVacation" -// : module == Module.Annual -// ? "Annual" -// : "Unknown" - -// export const dayAsPrismaEnum = (day: Day): $Enums.Day => -// day == Day.Sun -// ? "Sun" -// : day == Day.Mon -// ? "Mon" -// : day == Day.Tue -// ? "Tue" -// : day == Day.Wed -// ? "Wed" -// : day == Day.Thu -// ? "Thu" -// : day == Day.Fri -// ? "Fri" -// : day == Day.Sat -// ? "Sat" -// : day == Day.Intensive -// ? "Intensive" -// : day == Day.Appointment -// ? "Appointment" -// : day == Day.AnyTime -// ? "AnyTime" -// : "Unknown" - -// const scheduleAsPrisma = (schedule: { -// module: Module -// day: Day -// period: number -// room: string -// }) => ({ -// ...schedule, -// module: moduleAsPrismaEnum(schedule.module), -// day: dayAsPrismaEnum(schedule.day), -// }) - -const upsertCourseConnectUser = async (course: Course, userId: string) => { - // const existingCourse = await prisma.course.findUnique({ - // where: { code: course.code }, - // }) - - const update = { - - users: { - connect: [{ id: userId }], - }, - } - - return prisma.course - .update({ - where: { code: course.code }, - data: { - users: { - connect: [{ id: userId }] - }, - }, - }) -} - -const upsertCourses = async (courses: Course[], userId: string) => { - const upsertPromises = courses.map(async course => upsertCourseConnectUser(course, userId)) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return await prisma.$transaction(upsertPromises as PrismaPromise[]) -} - -export const resetUserCourses = async (userId: string) => { - return await prisma.user.update({ - where: { id: userId }, - data: { courses: { set: [] } }, - }) -} - -export const setCoursesForUser = async (courses: Course[], userId: string) => { - const r = await resetUserCourses(userId) - console.log("resetUserCourses:", r) - const s = await upsertCourses(courses, userId) - console.log("upsertCourses:", s) -} - export const db = { findAccount: async (userId: string): Promise => { const account = await prisma.account.findFirst({ @@ -118,35 +21,4 @@ export const db = { const users = await prisma.user.findMany() return users }, - - insertCoursesForUser: async (courses: Course[], userId: string) => { - const coursesConnected = courses.map(course => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { schedules, ...withoutSchedules } = course - return { - ...withoutSchedules, - schedules: { - connect: [{}], - }, - users: { - connect: [{ id: userId }], - }, - } - }) - - const res = await prisma.course.createMany({ data: coursesConnected }) - console.log(res) - const resUpd = await prisma.course.updateMany({ data: courses }) - console.log(resUpd) - }, - -} - -export const getUserCourseIds = async (userId: string): Promise => { - const userWithCourses = await prisma.user.findUnique({ - where: { id: userId }, - include: { courses: true } - }) - - return userWithCourses?.courses.map(({ code }) => code) } diff --git a/src/lib/server.ts b/src/lib/server.ts index d054f44..bc0b83a 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -1,13 +1,48 @@ "use server" import { Course } from "@/third-party/twinte-parser-type" -import { getUserCourseIds, prisma, setCoursesForUser } from "./prisma" -export const insertCoursesForUserOnFileLoad = async (courses: Course[], userId: string) => { - // const res = await db.insertCoursesForUser(courses, userId) - await setCoursesForUser(courses, userId) +import { prisma } from "./prisma" + +export async function resetAndUpdateUserCourses(userId: string, courses: Course[]) { + try { + await prisma.$transaction(async tx => { + // ユーザーの既存のコースをすべて削除 + await tx.course.deleteMany({ + where: { userId: userId }, + }) + + // 新しいコースを追加 + await tx.course.createMany({ + data: courses.map(course => ({ + userId: userId, + code: course.code, + })), + }) + }) + + return { success: true } + } catch (error) { + console.error("ユーザーのコース更新中にエラーが発生しました:", error) + return { success: false, error: "コースの更新に失敗しました" } + } } -export const getUserCourses = async (userId: string): Promise => { - const courseIds = await getUserCourseIds(userId) - if (courseIds == undefined) return undefined +export async function getUserCourseIds(userId: string): Promise { + try { + const userCourses = await prisma.course.findMany({ + where: { userId: userId }, + }) + + // ここでは、Course型に合わせてデータを整形する必要があります + // 実際のCourse型の構造に応じて、必要な情報を追加してください + const courses = userCourses.map(course => ({ + code: course.code, + // 他の必要なフィールドをここに追加 + // 例: name: '未設定', // または、別のクエリでコース名を取得する + })) + return courses.map(({ code }) => code) + } catch (error) { + console.error("ユーザーのコース取得中にエラーが発生しました:", error) + throw new Error("コースの取得に失敗しました") + } } From 98463ebc5b49a42d4924e8dbd053aa57faa93451 Mon Sep 17 00:00:00 2001 From: uxiun Date: Fri, 17 Jan 2025 22:14:54 +0900 Subject: [PATCH 9/9] =?UTF-8?q?=E6=8B=9B=E5=BE=85=E7=9B=B8=E6=89=8B?= =?UTF-8?q?=E3=81=AE=E6=8E=88=E6=A5=AD=E6=99=82=E9=96=93=E3=82=92=E8=80=83?= =?UTF-8?q?=E6=85=AE=E3=81=97=E3=81=A6=E7=A9=BA=E3=81=8D=E6=99=82=E9=96=93?= =?UTF-8?q?=E6=A4=9C=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/adjust/WeekView.tsx | 9 ++------ src/app/adjust/candidate.tsx | 45 ++++++++++++++++++++++++++---------- src/app/adjust/client.tsx | 1 + src/app/adjust/page.tsx | 4 ++-- src/lib/server.ts | 2 +- 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/app/adjust/WeekView.tsx b/src/app/adjust/WeekView.tsx index 7828901..f36716b 100644 --- a/src/app/adjust/WeekView.tsx +++ b/src/app/adjust/WeekView.tsx @@ -11,7 +11,7 @@ type WeekViewProps = { currentDate: Date handlePeriodClick: (period: Period) => Promise isButtonActive: boolean - courses: Course[] + coursePeriods: CoursePeriod[] } const handleClassClick = (courseWithPeriod: CourseWithPeriod) => { @@ -33,7 +33,7 @@ export function WeekView({ currentDate, handlePeriodClick, isButtonActive, - courses, + coursePeriods, }: WeekViewProps) { const weekDates = Array.from(Array(7).keys()).map(i => { const date = new Date(currentDate) @@ -42,11 +42,6 @@ export function WeekView({ }) const hours = Array.from({ length: 24 }, (_, i) => i) - const coursePeriods: CoursePeriod[] = courses.map(course => ({ - course, - periods: courseToPeriods(currentDate, course), - })) - console.log("coursePeriods:", coursePeriods) /** @todo Reduce this TOO DEEP nest */ diff --git a/src/app/adjust/candidate.tsx b/src/app/adjust/candidate.tsx index 15fa49d..8dd1ab3 100644 --- a/src/app/adjust/candidate.tsx +++ b/src/app/adjust/candidate.tsx @@ -10,7 +10,8 @@ import { toast } from "react-toastify" import { WeekView } from "./WeekView" import { Course } from "@/third-party/twinte-parser-type" import { YesNoDialog } from "@/components/ui/dialog" -import { courseToPeriods } from "@/lib/course" +import { CoursePeriod, courseToPeriods } from "@/lib/course" +import { getUserCourseCodes } from "@/lib/server" type Props = { title: string @@ -20,6 +21,7 @@ type Props = { selectedDurationMinute: number /** 履修中の講義一覧 */ courses: Course[] + allCourses: Course[] } export default function Candidate(props: Props) { @@ -34,22 +36,41 @@ export default function Candidate(props: Props) { setIsButtonActive(props.title.trim() !== "") }, [props.title]) + // 自分の授業とその時間の配列 + const coursePeriods: CoursePeriod[] = props.courses.map(course => ({ + course, + periods: courseToPeriods(new Date(), course), + })) + async function handleSchedule() { setIsButtonActive(false) try { - // 科目に対する授業時間の配列を求める - const classPeriods: Period[] = props.courses - .map(course => { - const periods = courseToPeriods(new Date(), course) - console.debug("科目に対する授業時間の配列", periods) - return periods - }) - .flat() + // 招待相手ごとの授業とその時間の配列 + const coursePeriodsOfGuests: CoursePeriod[][] = ( + await Promise.all(props.selectedUserIds.map(getUserCourseCodes)) + ).map(codes => + props.allCourses + .filter(({ code }) => codes.includes(code)) + .map(course => ({ + course, + periods: courseToPeriods(new Date(), course), + })), + ) + + console.debug("自分の授業とその時間の配列", coursePeriods) + console.debug("招待相手ごとの授業とその時間の配列", coursePeriodsOfGuests) + + // 自分と招待相手全員を合わせた授業時間の配列 + const classPeriodsOfUsers: Period[] = [...coursePeriodsOfGuests, coursePeriods].flatMap( + coursePeriods => coursePeriods.flatMap(({ periods }) => periods), + ) + + console.debug("すべての授業の、授業時間の配列", classPeriodsOfUsers) + const periods = [ ...(await periodsOfUsers(props.selectedUserIds, props.excludePeriod)), - classPeriods, + classPeriodsOfUsers, ] - console.debug("すべての授業の、授業時間の配列", classPeriods) const freePeriods = await findFreePeriods(props.selectedDurationMinute, periods) console.log("freePfreePeriods:", freePeriods) @@ -130,7 +151,7 @@ export default function Candidate(props: Props) { handlePeriodClick={handlePeriodClick} periods={freePeriods} isButtonActive={isButtonActive} - courses={props.courses} + coursePeriods={coursePeriods} /> ) : ( "" diff --git a/src/app/adjust/client.tsx b/src/app/adjust/client.tsx index 73264e6..5b80f41 100644 --- a/src/app/adjust/client.tsx +++ b/src/app/adjust/client.tsx @@ -116,6 +116,7 @@ export default function SchedulePlanner(props: { title={title} users={users} courses={courses} + allCourses={allCourses} />
diff --git a/src/app/adjust/page.tsx b/src/app/adjust/page.tsx index f7be30b..7fd08b5 100644 --- a/src/app/adjust/page.tsx +++ b/src/app/adjust/page.tsx @@ -4,7 +4,7 @@ import { getServerSession } from "next-auth" import { authOptions } from "@/lib/auth" import { fetchCourses } from "@/third-party/twinte-parser" import { Course } from "@/third-party/twinte-parser-type" -import { getUserCourseIds } from "@/lib/server" +import { getUserCourseCodes } from "@/lib/server" export default async function AdjustPage() { const session = await getServerSession(authOptions) @@ -12,7 +12,7 @@ export default async function AdjustPage() { const allCourses = (await fetchCourses()) as Course[] // console.log(allCourses) - const userCourseIds = session?.user.id ? await getUserCourseIds(session.user.id) : undefined + const userCourseIds = session?.user.id ? await getUserCourseCodes(session.user.id) : undefined console.log("userCourseIds:", userCourseIds) const courses = allCourses.filter(course => userCourseIds?.includes(course.code)) diff --git a/src/lib/server.ts b/src/lib/server.ts index bc0b83a..f01cd53 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -26,7 +26,7 @@ export async function resetAndUpdateUserCourses(userId: string, courses: Course[ } } -export async function getUserCourseIds(userId: string): Promise { +export async function getUserCourseCodes(userId: string): Promise { try { const userCourses = await prisma.course.findMany({ where: { userId: userId },