Skip to content

Commit d864d91

Browse files
chenxin-yanManus Agent
andauthored
feat: add schools (#64)
Co-authored-by: Manus Agent <manus@agent.ai>
1 parent a654071 commit d864d91

File tree

19 files changed

+445
-55
lines changed

19 files changed

+445
-55
lines changed

apps/scraper/src/modules/courses/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** biome-ignore-all lint/correctness/noUnusedFunctionParameters: bypass for now */
22
import type {
3-
ZUpsertCourse,
3+
ZUpsertCourseWithPrerequisites,
44
ZUpsertPrerequisites,
55
} from "@albert-plus/server/convex/http";
66
import type { DrizzleD1Database } from "drizzle-orm/d1";
@@ -23,7 +23,7 @@ export async function scrapeCourse(
2323
db: DrizzleD1Database,
2424
env: CloudflareBindings,
2525
): Promise<{
26-
course: z.infer<typeof ZUpsertCourse>;
26+
course: Omit<z.infer<typeof ZUpsertCourseWithPrerequisites>, "prerequisites">;
2727
prerequisites: CoursePrerequisite[];
2828
}> {
2929
// TODO: implement this function

apps/scraper/src/modules/programs/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** biome-ignore-all lint/correctness/noUnusedFunctionParameters: bypass for now */
22
import type {
3-
ZUpsertProgram,
3+
ZUpsertProgramWithRequirements,
44
ZUpsertRequirements,
55
} from "@albert-plus/server/convex/http";
66
import type { DrizzleD1Database } from "drizzle-orm/d1";
@@ -23,7 +23,7 @@ export async function scrapeProgram(
2323
db: DrizzleD1Database,
2424
env: CloudflareBindings,
2525
): Promise<{
26-
program: z.infer<typeof ZUpsertProgram>;
26+
program: Omit<z.infer<typeof ZUpsertProgramWithRequirements>, "requirements">;
2727
requirements: ProgramRequirement[];
2828
}> {
2929
// TODO: implement this function

apps/web/src/app/sign-in/sso-callback/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export default function Page() {
99

1010
{/* Required for sign-up flows
1111
Clerk's bot sign-up protection is enabled by default */}
12-
{/** biome-ignore lint/correctness/useUniqueElementIds: handled by clerk */}
1312
<div id="clerk-captcha" />
1413
</>
1514
);

packages/server/convex/_generated/api.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import type * as schemas_appConfigs from "../schemas/appConfigs.js";
2020
import type * as schemas_courseOfferings from "../schemas/courseOfferings.js";
2121
import type * as schemas_courses from "../schemas/courses.js";
2222
import type * as schemas_programs from "../schemas/programs.js";
23+
import type * as schemas_schools from "../schemas/schools.js";
2324
import type * as schemas_students from "../schemas/students.js";
25+
import type * as schools from "../schools.js";
2426
import type * as seed from "../seed.js";
2527
import type * as students from "../students.js";
2628
import type * as userCourseOfferings from "../userCourseOfferings.js";
@@ -53,7 +55,9 @@ declare const fullApi: ApiFromModules<{
5355
"schemas/courseOfferings": typeof schemas_courseOfferings;
5456
"schemas/courses": typeof schemas_courses;
5557
"schemas/programs": typeof schemas_programs;
58+
"schemas/schools": typeof schemas_schools;
5659
"schemas/students": typeof schemas_students;
60+
schools: typeof schools;
5761
seed: typeof seed;
5862
students: typeof students;
5963
userCourseOfferings: typeof userCourseOfferings;

packages/server/convex/courses.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getManyFrom } from "convex-helpers/server/relationships";
44
import { internalMutation } from "./_generated/server";
55
import { protectedQuery } from "./helpers/auth";
66
import { courses } from "./schemas/courses";
7+
import { schoolName } from "./schemas/schools";
78

89
export const getCourseById = protectedQuery({
910
args: { id: v.id("courses") },
@@ -68,9 +69,62 @@ export const getCourses = protectedQuery({
6869
args: {
6970
level: v.number(),
7071
query: v.optional(v.string()),
72+
schools: v.optional(v.array(schoolName)),
7173
paginationOpts: paginationOptsValidator,
7274
},
7375
handler: async (ctx, args) => {
76+
if (args.schools && args.schools.length > 0) {
77+
if (args.query !== undefined) {
78+
const results = await Promise.all(
79+
args.schools.map((school) =>
80+
ctx.db
81+
.query("courses")
82+
.withSearchIndex("search_title", (q) =>
83+
q
84+
.search("title", args.query as string)
85+
.eq("level", args.level)
86+
.eq("school", school),
87+
)
88+
.paginate(args.paginationOpts),
89+
),
90+
);
91+
92+
const allCourses = results.flatMap((result) => result.page);
93+
const continueCursor = results.find(
94+
(result) => result.isDone === false,
95+
)?.continueCursor;
96+
97+
return {
98+
page: allCourses,
99+
isDone: results.every((result) => result.isDone),
100+
continueCursor: continueCursor ?? null,
101+
};
102+
}
103+
104+
const results = await Promise.all(
105+
args.schools.map((school) =>
106+
ctx.db
107+
.query("courses")
108+
.withIndex("by_school_level", (q) =>
109+
q.eq("school", school).eq("level", args.level),
110+
)
111+
.order("desc")
112+
.paginate(args.paginationOpts),
113+
),
114+
);
115+
116+
const allCourses = results.flatMap((result) => result.page);
117+
const continueCursor = results.find(
118+
(result) => result.isDone === false,
119+
)?.continueCursor;
120+
121+
return {
122+
page: allCourses,
123+
isDone: results.every((result) => result.isDone),
124+
continueCursor: continueCursor ?? null,
125+
};
126+
}
127+
74128
if (args.query !== undefined) {
75129
return await ctx.db
76130
.query("courses")

packages/server/convex/http.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,27 @@ import type { Id } from "./_generated/dataModel";
55
import { apiAction } from "./helpers/auth";
66
import { AppConfigKey } from "./schemas/appConfigs";
77

8-
export const ZUpsertCourse = z.object({
9-
program: z.string(),
10-
code: z.string(),
11-
level: z.coerce.number(),
12-
title: z.string(),
13-
credits: z.int(),
14-
description: z.string(),
15-
courseUrl: z.string(),
16-
});
8+
const ZSchoolName = z.enum([
9+
"College of Arts and Science",
10+
"Graduate School of Arts and Science",
11+
"College of Dentistry",
12+
"Gallatin School of Individualized Study",
13+
"Leonard N. Stern School of Business",
14+
"Liberal Studies",
15+
"NYU Abu Dhabi",
16+
"NYU Shanghai",
17+
"NYU Grossman School of Medicine",
18+
"NYU Grossman Long Island School of Medicine",
19+
"Robert F. Wagner Graduate School of Public Service",
20+
"Rory Meyers College of Nursing",
21+
"School of Global Public Health",
22+
"School of Law",
23+
"School of Professional Studies",
24+
"Silver School of Social Work",
25+
"Steinhardt School of Culture, Education, and Human Development",
26+
"Tandon School of Engineering",
27+
"Tisch School of the Arts",
28+
] as const);
1729

1830
export const ZUpsertCourseWithPrerequisites = z.object({
1931
program: z.string(),
@@ -23,6 +35,7 @@ export const ZUpsertCourseWithPrerequisites = z.object({
2335
credits: z.int(),
2436
description: z.string(),
2537
courseUrl: z.string(),
38+
school: ZSchoolName,
2639
prerequisites: z.array(
2740
z.discriminatedUnion("type", [
2841
z.object({
@@ -42,15 +55,10 @@ export const ZUpsertCourseWithPrerequisites = z.object({
4255
),
4356
});
4457

45-
export const ZUpsertProgram = z.object({
46-
name: z.string(),
47-
level: z.enum(["undergraduate", "graduate"]),
48-
programUrl: z.string(),
49-
});
50-
5158
export const ZUpsertProgramWithRequirements = z.object({
5259
name: z.string(),
5360
level: z.enum(["undergraduate", "graduate"]),
61+
school: ZSchoolName,
5462
programUrl: z.string(),
5563
requirements: z.array(
5664
z.discriminatedUnion("type", [

packages/server/convex/programs.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getManyFrom } from "convex-helpers/server/relationships";
44
import { internalMutation } from "./_generated/server";
55
import { protectedQuery } from "./helpers/auth";
66
import { programs } from "./schemas/programs";
7+
import { schoolName } from "./schemas/schools";
78

89
export const getProgramById = protectedQuery({
910
args: { id: v.id("programs") },
@@ -62,9 +63,57 @@ export const getProgramByName = protectedQuery({
6263
export const getPrograms = protectedQuery({
6364
args: {
6465
query: v.optional(v.string()),
66+
schools: v.optional(v.array(schoolName)),
6567
paginationOpts: paginationOptsValidator,
6668
},
67-
handler: async (ctx, { query, paginationOpts }) => {
69+
handler: async (ctx, { query, schools, paginationOpts }) => {
70+
if (schools && schools.length > 0) {
71+
if (query) {
72+
const results = await Promise.all(
73+
schools.map((school) =>
74+
ctx.db
75+
.query("programs")
76+
.withSearchIndex("search_name", (q) =>
77+
q.search("name", query).eq("school", school),
78+
)
79+
.paginate(paginationOpts),
80+
),
81+
);
82+
83+
const allPrograms = results.flatMap((result) => result.page);
84+
const continueCursor = results.find(
85+
(result) => result.isDone === false,
86+
)?.continueCursor;
87+
88+
return {
89+
page: allPrograms,
90+
isDone: results.every((result) => result.isDone),
91+
continueCursor: continueCursor ?? null,
92+
};
93+
}
94+
95+
const results = await Promise.all(
96+
schools.map((school) =>
97+
ctx.db
98+
.query("programs")
99+
.withIndex("by_school", (q) => q.eq("school", school))
100+
.order("desc")
101+
.paginate(paginationOpts),
102+
),
103+
);
104+
105+
const allPrograms = results.flatMap((result) => result.page);
106+
const continueCursor = results.find(
107+
(result) => result.isDone === false,
108+
)?.continueCursor;
109+
110+
return {
111+
page: allPrograms,
112+
isDone: results.every((result) => result.isDone),
113+
continueCursor: continueCursor ?? null,
114+
};
115+
}
116+
68117
if (query) {
69118
return await ctx.db
70119
.query("programs")

packages/server/convex/schema.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,26 @@ import {
66
} from "./schemas/courseOfferings";
77
import { courses, prerequisites, userCourses } from "./schemas/courses";
88
import { programs, requirements } from "./schemas/programs";
9+
import { schools } from "./schemas/schools";
910
import { students } from "./schemas/students";
1011

1112
export default defineSchema({
1213
appConfigs: defineTable(appConfigs).index("by_key", ["key"]),
1314
programs: defineTable(programs)
1415
.index("by_program_name", ["name"])
16+
.index("by_school", ["school"])
1517
.searchIndex("search_name", {
1618
searchField: "name",
19+
filterFields: ["school"],
1720
}),
1821
requirements: defineTable(requirements).index("by_program", ["programId"]),
1922
courses: defineTable(courses)
2023
.index("by_course_code", ["code"])
2124
.index("by_level", ["level"])
25+
.index("by_school_level", ["school", "level"])
2226
.searchIndex("search_title", {
2327
searchField: "title",
24-
filterFields: ["level"],
28+
filterFields: ["level", "school"],
2529
}),
2630
prerequisites: defineTable(prerequisites).index("by_course", ["courseId"]),
2731
courseOfferings: defineTable(courseOfferings)
@@ -40,4 +44,5 @@ export default defineSchema({
4044
"userId",
4145
]),
4246
students: defineTable(students).index("by_user_id", ["userId"]),
47+
schools: defineTable(schools).index("by_school_name", ["name"]),
4348
});

packages/server/convex/schemas/courses.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { v } from "convex/values";
2+
import { schoolName } from "./schools";
23

34
const courses = {
45
code: v.string(), // CSCI-UA 101
56
program: v.string(), // CSCI-UA
67
level: v.number(), // 100
78
title: v.string(), // Intro to Computer Science
89
credits: v.number(), // 4
10+
school: schoolName,
911
description: v.string(),
1012
courseUrl: v.string(),
1113
};

packages/server/convex/schemas/programs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { v } from "convex/values";
2+
import { schoolName } from "./schools";
23

34
const programs = {
45
name: v.string(), // Computer Science (BA)
56
level: v.union(v.literal("undergraduate"), v.literal("graduate")),
7+
school: schoolName,
68
programUrl: v.string(),
79
};
810

0 commit comments

Comments
 (0)