feat(core-edu): 完整实现 core-edu 教学核心服务
包含 classes/exams/homework/grades/attendance/scheduling 域、outbox、iam-consumer、redis 配置等完整实现
This commit is contained in:
82
services/core-edu/src/scheduling/schedule-conflict.ts
Normal file
82
services/core-edu/src/scheduling/schedule-conflict.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 排课冲突检测(纯函数)
|
||||
*
|
||||
* 冲突维度(P3):
|
||||
* - teacher: 同一教师在同一时间段有排课冲突
|
||||
* - class: 同一班级在同一时间段有排课冲突
|
||||
*
|
||||
* room_id 仅预留字段(ISSUE-006 决策 #2),P3 不校验教室冲突。
|
||||
*/
|
||||
|
||||
export interface ScheduleSlot {
|
||||
id: string;
|
||||
teacherId: string;
|
||||
classId: string;
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
roomId?: string | null;
|
||||
}
|
||||
|
||||
export interface ConflictResult {
|
||||
hasConflict: boolean;
|
||||
conflictingSlot?: ScheduleSlot;
|
||||
conflictType: "teacher" | "class" | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查时间区间重叠。
|
||||
* [startA, endA) 与 [startB, endB) 重叠的条件:startA < endB && startB < endA
|
||||
*/
|
||||
export function isTimeOverlap(
|
||||
startA: Date,
|
||||
endA: Date,
|
||||
startB: Date,
|
||||
endB: Date,
|
||||
): boolean {
|
||||
return startA < endB && startB < endA;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查新排课与现有排课列表的冲突。
|
||||
* 排除自身(如果是更新场景,传入 excludeId)。
|
||||
*/
|
||||
export function detectConflict(
|
||||
newSlot: Omit<ScheduleSlot, "id"> & { id?: string },
|
||||
existingSlots: ScheduleSlot[],
|
||||
excludeId?: string,
|
||||
): ConflictResult {
|
||||
for (const slot of existingSlots) {
|
||||
if (excludeId && slot.id === excludeId) continue;
|
||||
|
||||
if (
|
||||
!isTimeOverlap(
|
||||
newSlot.startTime,
|
||||
newSlot.endTime,
|
||||
slot.startTime,
|
||||
slot.endTime,
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 教师冲突
|
||||
if (newSlot.teacherId === slot.teacherId) {
|
||||
return {
|
||||
hasConflict: true,
|
||||
conflictingSlot: slot,
|
||||
conflictType: "teacher",
|
||||
};
|
||||
}
|
||||
|
||||
// 班级冲突
|
||||
if (newSlot.classId === slot.classId) {
|
||||
return {
|
||||
hasConflict: true,
|
||||
conflictingSlot: slot,
|
||||
conflictType: "class",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { hasConflict: false, conflictType: null };
|
||||
}
|
||||
157
services/core-edu/src/scheduling/scheduling.controller.ts
Normal file
157
services/core-edu/src/scheduling/scheduling.controller.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { Body, Controller, Get, Param, Post, Req } from "@nestjs/common";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
SchedulingService,
|
||||
type CreateCourseInput,
|
||||
type CreateScheduleInput,
|
||||
} from "./scheduling.service.js";
|
||||
import {
|
||||
Permissions,
|
||||
RequirePermission,
|
||||
} from "../middleware/permission.guard.js";
|
||||
import type { AuthenticatedRequest } from "../middleware/auth.middleware.js";
|
||||
import {
|
||||
UnauthorizedError,
|
||||
ValidationError,
|
||||
} from "../shared/errors/application-error.js";
|
||||
|
||||
const createCourseSchema = z.object({
|
||||
classId: z.string().min(1),
|
||||
subjectId: z.string().min(1),
|
||||
teacherId: z.string().min(1),
|
||||
schoolId: z.string().min(1),
|
||||
name: z.string().min(1).max(200),
|
||||
startDate: z.string().min(1),
|
||||
endDate: z.string().min(1),
|
||||
});
|
||||
|
||||
const createScheduleSchema = z.object({
|
||||
courseId: z.string().min(1),
|
||||
lessonId: z.string().optional(),
|
||||
teacherId: z.string().min(1),
|
||||
classId: z.string().min(1),
|
||||
roomId: z.string().optional(),
|
||||
startTime: z.string().min(1),
|
||||
endTime: z.string().min(1),
|
||||
schoolId: z.string().min(1),
|
||||
});
|
||||
|
||||
@Controller("v1")
|
||||
export class SchedulingController {
|
||||
constructor(private readonly schedulingService: SchedulingService) {}
|
||||
|
||||
// ----- Course endpoints -----
|
||||
|
||||
@Post("courses")
|
||||
@RequirePermission(Permissions.COURSE_CREATE)
|
||||
async createCourse(
|
||||
@Body() body: unknown,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
): Promise<{ success: true; data: { id: string } }> {
|
||||
const userId = req.userId;
|
||||
if (!userId) {
|
||||
throw new UnauthorizedError("Missing x-user-id header");
|
||||
}
|
||||
const parsed = createCourseSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
throw new ValidationError("Invalid course input", parsed.error.flatten());
|
||||
}
|
||||
const input: CreateCourseInput = parsed.data;
|
||||
const result = await this.schedulingService.createCourse(input);
|
||||
return { success: true, data: result };
|
||||
}
|
||||
|
||||
@Get("courses/:id")
|
||||
@RequirePermission(Permissions.COURSE_READ)
|
||||
async getCourse(@Param("id") id: string): Promise<{
|
||||
success: true;
|
||||
data: Awaited<ReturnType<SchedulingService["getCourse"]>>;
|
||||
}> {
|
||||
const data = await this.schedulingService.getCourse(id);
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Get("courses/class/:classId")
|
||||
@RequirePermission(Permissions.COURSE_READ)
|
||||
async listCoursesByClass(@Param("classId") classId: string): Promise<{
|
||||
success: true;
|
||||
data: Awaited<ReturnType<SchedulingService["listCoursesByClass"]>>;
|
||||
}> {
|
||||
const data = await this.schedulingService.listCoursesByClass(classId);
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Get("courses/teacher/:teacherId")
|
||||
@RequirePermission(Permissions.COURSE_READ)
|
||||
async listCoursesByTeacher(@Param("teacherId") teacherId: string): Promise<{
|
||||
success: true;
|
||||
data: Awaited<ReturnType<SchedulingService["listCoursesByTeacher"]>>;
|
||||
}> {
|
||||
const data = await this.schedulingService.listCoursesByTeacher(teacherId);
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
// ----- Schedule endpoints -----
|
||||
|
||||
@Post("schedules")
|
||||
@RequirePermission(Permissions.SCHEDULE_CREATE)
|
||||
async createSchedule(
|
||||
@Body() body: unknown,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
): Promise<{ success: true; data: { id: string } }> {
|
||||
const userId = req.userId;
|
||||
if (!userId) {
|
||||
throw new UnauthorizedError("Missing x-user-id header");
|
||||
}
|
||||
const parsed = createScheduleSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
throw new ValidationError(
|
||||
"Invalid schedule input",
|
||||
parsed.error.flatten(),
|
||||
);
|
||||
}
|
||||
const input: CreateScheduleInput = parsed.data;
|
||||
const result = await this.schedulingService.createSchedule(input);
|
||||
return { success: true, data: result };
|
||||
}
|
||||
|
||||
@Get("schedules/:id")
|
||||
@RequirePermission(Permissions.SCHEDULE_READ)
|
||||
async getSchedule(@Param("id") id: string): Promise<{
|
||||
success: true;
|
||||
data: Awaited<ReturnType<SchedulingService["getSchedule"]>>;
|
||||
}> {
|
||||
const data = await this.schedulingService.getSchedule(id);
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Get("schedules/course/:courseId")
|
||||
@RequirePermission(Permissions.SCHEDULE_READ)
|
||||
async listSchedulesByCourse(@Param("courseId") courseId: string): Promise<{
|
||||
success: true;
|
||||
data: Awaited<ReturnType<SchedulingService["listSchedulesByCourse"]>>;
|
||||
}> {
|
||||
const data = await this.schedulingService.listSchedulesByCourse(courseId);
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Get("schedules/teacher/:teacherId")
|
||||
@RequirePermission(Permissions.SCHEDULE_READ)
|
||||
async listSchedulesByTeacher(@Param("teacherId") teacherId: string): Promise<{
|
||||
success: true;
|
||||
data: Awaited<ReturnType<SchedulingService["listSchedulesByTeacher"]>>;
|
||||
}> {
|
||||
const data = await this.schedulingService.listSchedulesByTeacher(teacherId);
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Get("schedules/class/:classId")
|
||||
@RequirePermission(Permissions.SCHEDULE_READ)
|
||||
async listSchedulesByClass(@Param("classId") classId: string): Promise<{
|
||||
success: true;
|
||||
data: Awaited<ReturnType<SchedulingService["listSchedulesByClass"]>>;
|
||||
}> {
|
||||
const data = await this.schedulingService.listSchedulesByClass(classId);
|
||||
return { success: true, data };
|
||||
}
|
||||
}
|
||||
10
services/core-edu/src/scheduling/scheduling.module.ts
Normal file
10
services/core-edu/src/scheduling/scheduling.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { SchedulingController } from "./scheduling.controller.js";
|
||||
import { SchedulingService } from "./scheduling.service.js";
|
||||
|
||||
@Module({
|
||||
controllers: [SchedulingController],
|
||||
providers: [SchedulingService],
|
||||
exports: [SchedulingService],
|
||||
})
|
||||
export class SchedulingModule {}
|
||||
113
services/core-edu/src/scheduling/scheduling.repository.ts
Normal file
113
services/core-edu/src/scheduling/scheduling.repository.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { eq, and, lte, gte } from "drizzle-orm";
|
||||
import { db } from "../config/database.js";
|
||||
import {
|
||||
courses,
|
||||
schedules,
|
||||
type Course,
|
||||
type NewCourse,
|
||||
type Schedule,
|
||||
type NewSchedule,
|
||||
} from "./scheduling.schema.js";
|
||||
|
||||
export class SchedulingRepository {
|
||||
// Courses
|
||||
async findCourseById(id: string): Promise<Course | undefined> {
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(courses)
|
||||
.where(eq(courses.id, id))
|
||||
.limit(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
async findCoursesByClassId(classId: string): Promise<Course[]> {
|
||||
return db.select().from(courses).where(eq(courses.classId, classId));
|
||||
}
|
||||
|
||||
async findCoursesByTeacherId(teacherId: string): Promise<Course[]> {
|
||||
return db.select().from(courses).where(eq(courses.teacherId, teacherId));
|
||||
}
|
||||
|
||||
async createCourse(course: NewCourse): Promise<void> {
|
||||
await db.insert(courses).values(course);
|
||||
}
|
||||
|
||||
// Schedules
|
||||
async findScheduleById(id: string): Promise<Schedule | undefined> {
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(schedules)
|
||||
.where(eq(schedules.id, id))
|
||||
.limit(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
async findSchedulesByCourseId(courseId: string): Promise<Schedule[]> {
|
||||
return db.select().from(schedules).where(eq(schedules.courseId, courseId));
|
||||
}
|
||||
|
||||
async findSchedulesByTeacherId(teacherId: string): Promise<Schedule[]> {
|
||||
return db
|
||||
.select()
|
||||
.from(schedules)
|
||||
.where(eq(schedules.teacherId, teacherId));
|
||||
}
|
||||
|
||||
async findSchedulesByClassId(classId: string): Promise<Schedule[]> {
|
||||
return db.select().from(schedules).where(eq(schedules.classId, classId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find overlapping schedules for conflict detection.
|
||||
* Returns schedules where [startTime, endTime) overlaps with the given range
|
||||
* for a specific teacher or class.
|
||||
*/
|
||||
async findOverlappingSchedules(
|
||||
teacherId: string,
|
||||
classId: string,
|
||||
startTime: Date,
|
||||
endTime: Date,
|
||||
excludeId?: string,
|
||||
): Promise<Schedule[]> {
|
||||
// Query both teacher and class conflicts
|
||||
const teacherSchedules = await db
|
||||
.select()
|
||||
.from(schedules)
|
||||
.where(
|
||||
and(
|
||||
eq(schedules.teacherId, teacherId),
|
||||
lte(schedules.startTime, endTime),
|
||||
gte(schedules.endTime, startTime),
|
||||
),
|
||||
);
|
||||
const classSchedules = await db
|
||||
.select()
|
||||
.from(schedules)
|
||||
.where(
|
||||
and(
|
||||
eq(schedules.classId, classId),
|
||||
lte(schedules.startTime, endTime),
|
||||
gte(schedules.endTime, startTime),
|
||||
),
|
||||
);
|
||||
const all = [...teacherSchedules, ...classSchedules];
|
||||
// Deduplicate and exclude self
|
||||
const seen = new Set<string>();
|
||||
return all.filter((s) => {
|
||||
if (excludeId && s.id === excludeId) return false;
|
||||
if (seen.has(s.id)) return false;
|
||||
seen.add(s.id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
async createSchedule(schedule: NewSchedule): Promise<void> {
|
||||
await db.insert(schedules).values(schedule);
|
||||
}
|
||||
|
||||
async updateSchedule(id: string, data: Partial<NewSchedule>): Promise<void> {
|
||||
await db.update(schedules).set(data).where(eq(schedules.id, id));
|
||||
}
|
||||
}
|
||||
|
||||
export const schedulingRepository = new SchedulingRepository();
|
||||
87
services/core-edu/src/scheduling/scheduling.schema.ts
Normal file
87
services/core-edu/src/scheduling/scheduling.schema.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
mysqlTable,
|
||||
varchar,
|
||||
timestamp,
|
||||
char,
|
||||
datetime,
|
||||
date,
|
||||
int,
|
||||
json,
|
||||
index,
|
||||
} from "drizzle-orm/mysql-core";
|
||||
|
||||
// 课程表
|
||||
export const courses = mysqlTable(
|
||||
"core_edu_courses",
|
||||
{
|
||||
id: char("id", { length: 36 }).notNull().primaryKey(),
|
||||
classId: char("class_id", { length: 36 }).notNull(),
|
||||
subjectId: char("subject_id", { length: 36 }).notNull(),
|
||||
teacherId: char("teacher_id", { length: 36 }).notNull(),
|
||||
schoolId: char("school_id", { length: 36 }).notNull(),
|
||||
name: varchar("name", { length: 200 }).notNull(),
|
||||
startDate: date("start_date").notNull(),
|
||||
endDate: date("end_date").notNull(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow().onUpdateNow(),
|
||||
},
|
||||
(table) => ({
|
||||
idxCoursesClassId: index("idx_courses_class_id").on(table.classId),
|
||||
idxCoursesTeacherId: index("idx_courses_teacher_id").on(table.teacherId),
|
||||
}),
|
||||
);
|
||||
|
||||
// 课次表
|
||||
export const lessons = mysqlTable(
|
||||
"core_edu_lessons",
|
||||
{
|
||||
id: char("id", { length: 36 }).notNull().primaryKey(),
|
||||
courseId: char("course_id", { length: 36 }).notNull(),
|
||||
title: varchar("title", { length: 200 }).notNull(),
|
||||
order: int("order").notNull(),
|
||||
knowledgePointIds: json("knowledge_point_ids"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
idxLessonsCourseId: index("idx_lessons_course_id").on(table.courseId),
|
||||
}),
|
||||
);
|
||||
|
||||
// 排课表
|
||||
export const schedules = mysqlTable(
|
||||
"core_edu_schedules",
|
||||
{
|
||||
id: char("id", { length: 36 }).notNull().primaryKey(),
|
||||
courseId: char("course_id", { length: 36 }).notNull(),
|
||||
lessonId: char("lesson_id", { length: 36 }),
|
||||
teacherId: char("teacher_id", { length: 36 }).notNull(),
|
||||
classId: char("class_id", { length: 36 }).notNull(),
|
||||
roomId: char("room_id", { length: 36 }), // P3 仅预留,不校验冲突
|
||||
startTime: datetime("start_time").notNull(),
|
||||
endTime: datetime("end_time").notNull(),
|
||||
status: varchar("status", { length: 20 }).notNull().default("scheduled"),
|
||||
schoolId: char("school_id", { length: 36 }).notNull(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow().onUpdateNow(),
|
||||
},
|
||||
(table) => ({
|
||||
idxSchedulesTeacherTime: index("idx_schedules_teacher_time").on(
|
||||
table.teacherId,
|
||||
table.startTime,
|
||||
table.endTime,
|
||||
),
|
||||
idxSchedulesClassTime: index("idx_schedules_class_time").on(
|
||||
table.classId,
|
||||
table.startTime,
|
||||
table.endTime,
|
||||
),
|
||||
idxSchedulesCourseId: index("idx_schedules_course_id").on(table.courseId),
|
||||
}),
|
||||
);
|
||||
|
||||
export type Course = typeof courses.$inferSelect;
|
||||
export type NewCourse = typeof courses.$inferInsert;
|
||||
export type Lesson = typeof lessons.$inferSelect;
|
||||
export type NewLesson = typeof lessons.$inferInsert;
|
||||
export type Schedule = typeof schedules.$inferSelect;
|
||||
export type NewSchedule = typeof schedules.$inferInsert;
|
||||
164
services/core-edu/src/scheduling/scheduling.service.ts
Normal file
164
services/core-edu/src/scheduling/scheduling.service.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { schedulingRepository } from "./scheduling.repository.js";
|
||||
import { detectConflict, type ScheduleSlot } from "./schedule-conflict.js";
|
||||
import {
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
ApplicationError,
|
||||
CoreEduErrorCode,
|
||||
} from "../shared/errors/application-error.js";
|
||||
import type { Course, Schedule } from "./scheduling.schema.js";
|
||||
|
||||
export interface CreateCourseInput {
|
||||
classId: string;
|
||||
subjectId: string;
|
||||
teacherId: string;
|
||||
schoolId: string;
|
||||
name: string;
|
||||
startDate: Date | string;
|
||||
endDate: Date | string;
|
||||
}
|
||||
|
||||
export interface CreateScheduleInput {
|
||||
courseId: string;
|
||||
lessonId?: string;
|
||||
teacherId: string;
|
||||
classId: string;
|
||||
roomId?: string;
|
||||
startTime: Date | string;
|
||||
endTime: Date | string;
|
||||
schoolId: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SchedulingService {
|
||||
// Course operations
|
||||
async createCourse(input: CreateCourseInput): Promise<{ id: string }> {
|
||||
if (!input.classId || !input.subjectId || !input.teacherId || !input.name) {
|
||||
throw new ValidationError(
|
||||
"classId, subjectId, teacherId, name are required",
|
||||
);
|
||||
}
|
||||
const id = randomUUID();
|
||||
await schedulingRepository.createCourse({
|
||||
id,
|
||||
classId: input.classId,
|
||||
subjectId: input.subjectId,
|
||||
teacherId: input.teacherId,
|
||||
schoolId: input.schoolId,
|
||||
name: input.name,
|
||||
startDate:
|
||||
input.startDate instanceof Date
|
||||
? input.startDate
|
||||
: new Date(input.startDate),
|
||||
endDate:
|
||||
input.endDate instanceof Date ? input.endDate : new Date(input.endDate),
|
||||
});
|
||||
return { id };
|
||||
}
|
||||
|
||||
async getCourse(id: string): Promise<Course> {
|
||||
const course = await schedulingRepository.findCourseById(id);
|
||||
if (!course) {
|
||||
throw new NotFoundError(`Course ${id} not found`);
|
||||
}
|
||||
return course;
|
||||
}
|
||||
|
||||
async listCoursesByClass(classId: string): Promise<Course[]> {
|
||||
return schedulingRepository.findCoursesByClassId(classId);
|
||||
}
|
||||
|
||||
async listCoursesByTeacher(teacherId: string): Promise<Course[]> {
|
||||
return schedulingRepository.findCoursesByTeacherId(teacherId);
|
||||
}
|
||||
|
||||
// Schedule operations
|
||||
async createSchedule(input: CreateScheduleInput): Promise<{ id: string }> {
|
||||
if (
|
||||
!input.courseId ||
|
||||
!input.teacherId ||
|
||||
!input.classId ||
|
||||
!input.schoolId
|
||||
) {
|
||||
throw new ValidationError(
|
||||
"courseId, teacherId, classId, schoolId are required",
|
||||
);
|
||||
}
|
||||
|
||||
const startTime =
|
||||
input.startTime instanceof Date
|
||||
? input.startTime
|
||||
: new Date(input.startTime);
|
||||
const endTime =
|
||||
input.endTime instanceof Date ? input.endTime : new Date(input.endTime);
|
||||
|
||||
if (startTime >= endTime) {
|
||||
throw new ValidationError("startTime must be before endTime");
|
||||
}
|
||||
|
||||
// Conflict detection
|
||||
const overlapping = await schedulingRepository.findOverlappingSchedules(
|
||||
input.teacherId,
|
||||
input.classId,
|
||||
startTime,
|
||||
endTime,
|
||||
);
|
||||
|
||||
const newSlot: Omit<ScheduleSlot, "id"> = {
|
||||
teacherId: input.teacherId,
|
||||
classId: input.classId,
|
||||
startTime,
|
||||
endTime,
|
||||
roomId: input.roomId,
|
||||
};
|
||||
|
||||
const conflict = detectConflict(newSlot, overlapping);
|
||||
if (conflict.hasConflict && conflict.conflictingSlot) {
|
||||
throw new ApplicationError(
|
||||
CoreEduErrorCode.SCHEDULE_CONFLICT,
|
||||
`Schedule conflict detected (${conflict.conflictType}): overlaps with existing schedule ${conflict.conflictingSlot.id}`,
|
||||
409,
|
||||
{
|
||||
conflictType: conflict.conflictType,
|
||||
conflictingScheduleId: conflict.conflictingSlot.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const id = randomUUID();
|
||||
await schedulingRepository.createSchedule({
|
||||
id,
|
||||
courseId: input.courseId,
|
||||
lessonId: input.lessonId,
|
||||
teacherId: input.teacherId,
|
||||
classId: input.classId,
|
||||
roomId: input.roomId,
|
||||
startTime,
|
||||
endTime,
|
||||
schoolId: input.schoolId,
|
||||
});
|
||||
return { id };
|
||||
}
|
||||
|
||||
async getSchedule(id: string): Promise<Schedule> {
|
||||
const schedule = await schedulingRepository.findScheduleById(id);
|
||||
if (!schedule) {
|
||||
throw new NotFoundError(`Schedule ${id} not found`);
|
||||
}
|
||||
return schedule;
|
||||
}
|
||||
|
||||
async listSchedulesByCourse(courseId: string): Promise<Schedule[]> {
|
||||
return schedulingRepository.findSchedulesByCourseId(courseId);
|
||||
}
|
||||
|
||||
async listSchedulesByTeacher(teacherId: string): Promise<Schedule[]> {
|
||||
return schedulingRepository.findSchedulesByTeacherId(teacherId);
|
||||
}
|
||||
|
||||
async listSchedulesByClass(classId: string): Promise<Schedule[]> {
|
||||
return schedulingRepository.findSchedulesByClassId(classId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user