Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"formatter": {
"indentStyle": "space",
"indentWidth": 2
"indentWidth": 2,
"lineWidth": 80
},
"linter": {
"rules": {
Expand Down
Binary file removed bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type {
RelationshipID,
Relationship,
CourseID,
InterestSubjectID,
Slot,
Course,
Enrollment,
Expand Down
24 changes: 13 additions & 11 deletions common/zod/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,6 @@ export const IntroLongSchema = z
// .min(2, { message: "自己紹介文は2文字以上です" })
.max(225, { message: "自己紹介文は225文字以下です" });

export const InterestSubjectSchema = z.object({
id: z.number(),
name: z.string(),
group: z.string(),
});

export const InterestSchema = z.object({
userId: UserIDSchema,
subjectId: z.number(),
});

export const UserSchema = z.object({
id: UserIDSchema,
guid: GUIDSchema,
Expand Down Expand Up @@ -76,6 +65,8 @@ export const RelationshipSchema = z.object({

export const CourseIDSchema = z.string();

export const InterestSubjectIDSchema = z.number();

export const DaySchema = z.enum([
"mon",
"tue",
Expand Down Expand Up @@ -108,6 +99,17 @@ export const EnrollmentSchema = z.object({
courseId: CourseIDSchema,
});

export const InterestSubjectSchema = z.object({
id: InterestSubjectIDSchema,
name: z.string(),
group: z.string(),
});

export const InterestSchema = z.object({
userId: UserIDSchema,
subjectId: InterestSubjectIDSchema,
});

export const UserWithCoursesAndSubjectsSchema = UserSchema.extend({
courses: CourseSchema.array(),
interestSubjects: InterestSubjectSchema.array(),
Expand Down
2 changes: 2 additions & 0 deletions common/zod/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
InitSharedRoomSchema,
InitUserSchema,
InterestSchema,
InterestSubjectIDSchema,
InterestSubjectSchema,
IntroLongSchema,
IntroShortSchema,
Expand Down Expand Up @@ -57,6 +58,7 @@ export type Step1User = z.infer<typeof Step1UserSchema>;
export type RelationshipID = z.infer<typeof RelationshipIDSchema>;
export type Relationship = z.infer<typeof RelationshipSchema>;
export type CourseID = z.infer<typeof CourseIDSchema>;
export type InterestSubjectID = z.infer<typeof InterestSubjectIDSchema>;
export type Slot = z.infer<typeof SlotSchema>;
export type Course = z.infer<typeof CourseSchema>;
export type Enrollment = z.infer<typeof EnrollmentSchema>;
Expand Down
2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
export PRISMA_QUERY_ENGINE_LIBRARY="${prisma-engines}/lib/libquery_engine.node";
export PRISMA_INTROSPECTION_ENGINE_BINARY="${prisma-engines}/bin/introspection-engine";
export PRISMA_FMT_BINARY="${prisma-engines}/bin/prisma-fmt";

export LD_LIBRARY_PATH=${pkgs.stdenv.cc.cc.lib}/lib
'';
};
in
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
"license": "ISC",
"devDependencies": {
"@biomejs/biome": "^1.9.1",
"husky": "^9.1.4",
"lint-staged": "^15.2.10"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"react-hook-form": "^7.53.2",
"@types/bun": "^1.1.10",
"cspell": "^8.14.4",
"typescript": "^5.6.2",
"lefthook": "^1.8.2"
},
"dependencies": {
"lefthook": "^1.8.2",
"zod": "^3.23.8"
},
"trustedDependencies": ["@biomejs/biome", "lefthook"]
Expand Down
35 changes: 35 additions & 0 deletions server/src/database/interest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ export async function get(id: number): Promise<InterestSubject | null> {
return await prisma.interestSubject.findUnique({ where: { id } });
}

export async function create(name: string): Promise<InterestSubject> {
const existingTag = await prisma.interestSubject.findMany({
where: { name },
});
if (existingTag.length > 0) {
throw new Error("同名のタグがすでに存在します");
}
return await prisma.interestSubject.create({
data: {
name,
group: "", // TODO: 運用次第
},
});
}

export async function of(userId: UserID): Promise<InterestSubject[]> {
return await prisma.interest
.findMany({
Expand Down Expand Up @@ -37,3 +52,23 @@ export async function remove(userId: UserID, subjectId: number) {
},
});
}

export async function updateMultipleWithTransaction(
userId: UserID,
subjectIds: number[],
) {
return await prisma.$transaction(async (prisma) => {
await prisma.interest.deleteMany({
where: {
userId,
},
});

await prisma.interest.createMany({
data: subjectIds.map((subjectId) => ({
userId,
subjectId,
})),
});
});
}
2 changes: 2 additions & 0 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import coursesRoutes from "./router/courses";
import matchesRoutes from "./router/matches";
import pictureRoutes from "./router/picture";
import requestsRoutes from "./router/requests";
import subjectsRoutes from "./router/subjects";
import usersRoutes from "./router/users";

const app = express();
Expand Down Expand Up @@ -52,6 +53,7 @@ app.get("/", (_, res) => {
app.use("/picture", pictureRoutes);
app.use("/users", usersRoutes);
app.use("/courses", coursesRoutes);
app.use("/subjects", subjectsRoutes);
app.use("/requests", requestsRoutes);
app.use("/matches", matchesRoutes);
app.use("/chat", chatRoutes);
Expand Down
131 changes: 131 additions & 0 deletions server/src/router/subjects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import express, { type Request, type Response } from "express";
import * as interest from "../database/interest";
import { safeGetUserId } from "../firebase/auth/db";

const router = express.Router();

router.get("/userId/:userId", async (req: Request, res: Response) => {
const userId = Number.parseInt(req.params.userId);
if (Number.isNaN(userId)) {
return res.status(400).json({ error: "Invalid userId" });
}
try {
const subjects = await interest.of(userId);
res.status(200).json(subjects);
} catch (error) {
console.error("Error fetching subjects by userId:", error);
res.status(500).json({ error: "Failed to fetch subjects by userId" });
}
});

router.get("/mine", async (req: Request, res: Response) => {
const userId = await safeGetUserId(req);
if (!userId.ok) return res.status(401).send("auth error");
try {
const subjects = await interest.of(userId.value);
res.status(200).json(subjects);
} catch (error) {
console.error("Error fetching subjects:", error);
res.status(500).json({ error: "Failed to fetch subjects" });
}
});

router.post("/", async (req: Request, res: Response) => {
const { name } = req.body;
if (typeof name !== "string") {
return res.status(400).json({ error: "name must be a string" });
}
try {
const newSubject = await interest.create(name);
res.status(201).json(newSubject);
} catch (error) {
console.error("Error creating subject:", error);
res.status(500).json({ error: "Failed to create subject" });
}
});

router.patch("/mine", async (req: Request, res: Response) => {
const userId = await safeGetUserId(req);
if (!userId.ok) return res.status(401).send("auth error");
const { subjectId } = req.body;
try {
const newSubject = await interest.get(subjectId);
if (!newSubject) {
return res.status(404).json({ error: "Subject not found" });
}
} catch (err) {
console.error("Error fetching subject:", err);
res.status(500).json({ error: "Failed to fetch subject" });
}
try {
const updatedSubjects = await interest.add(userId.value, subjectId);
res.status(200).json(updatedSubjects);
} catch (error) {
console.error("Error updating subjects:", error);
res.status(500).json({ error: "Failed to update subjects" });
}
});

router.delete("/mine", async (req: Request, res: Response) => {
const userId = await safeGetUserId(req);
if (!userId.ok) return res.status(401).send("auth error");
const { subjectId } = req.body;
try {
const newSubject = await interest.get(subjectId);
if (!newSubject) {
return res.status(404).json({ error: "Subject not found" });
}
} catch (err) {
console.error("Error fetching subject:", err);
res.status(500).json({ error: "Failed to fetch subject" });
}
try {
const updatedSubjects = await interest.remove(userId.value, subjectId);
res.status(200).json(updatedSubjects);
} catch (error) {
console.error("Error deleting subjects:", error);
res.status(500).json({ error: "Failed to delete subjects" });
}
});

router.put("/mine", async (req: Request, res: Response) => {
const userId = await safeGetUserId(req);
if (!userId.ok) return res.status(401).send("auth error");
const { subjectIds } = req.body;
if (!Array.isArray(subjectIds)) {
return res.status(400).json({ error: "subjectIds must be an array" });
}
try {
const newSubjects = await Promise.all(
subjectIds.map((id) => interest.get(id)),
);
if (newSubjects.some((s) => !s)) {
return res.status(404).json({ error: "Subject not found" });
}
} catch (err) {
console.error("Error fetching subjects:", err);
res.status(500).json({ error: "Failed to fetch subjects" });
}
try {
const updatedSubjects = await interest.updateMultipleWithTransaction(
userId.value,
subjectIds,
);
res.status(200).json(updatedSubjects);
} catch (error) {
console.error("Error updating subjects:", error);
res.status(500).json({ error: "Failed to update subjects" });
}
});

router.get("/all", async (req: Request, res: Response) => {
try {
const subjects = await interest.all();
res.status(200).json(subjects);
} catch (error) {
console.error("Error fetching subjects:", error);
res.status(500).json({ error: "Failed to fetch subjects" });
}
});

export default router;
11 changes: 6 additions & 5 deletions server/src/seeds/seed-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import {
async function main() {
await Promise.all(
subjects.map(async ({ group, subjects }) => {
for (const [id, name] of subjects) {
await prisma.interestSubject.upsert({
where: { id },
update: { name, group },
create: { id, name, group },
for (const [name] of subjects) {
await prisma.interestSubject.create({
data: {
name,
group,
},
});
}
}),
Expand Down
12 changes: 6 additions & 6 deletions server/src/seeds/test-data/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ import type { Day } from "common/types";

export const subjects: Array<{
group: string;
subjects: Array<[number, string]>;
subjects: Array<[string]>;
}> = [
{
group: "Computer Science",
subjects: [
[1, "型システム"],
[2, "機械学習"],
[3, "CPU アーキテクチャ"],
[4, "分散処理"],
["型システム"],
["機械学習"],
["CPU アーキテクチャ"],
["分散処理"],
] as const,
},
{
group: "Math",
subjects: [[5, "Lean4"]],
subjects: [["Lean4"]],
},
];
export const interest = [
Expand Down
Loading
Loading