diff --git a/common/types.ts b/common/types.ts
index c89a5b5e..dc705e6c 100644
--- a/common/types.ts
+++ b/common/types.ts
@@ -7,6 +7,7 @@ export type {
Gender,
RelationshipStatus,
InterestSubject,
+ Interest,
User,
InitUser,
UpdateUser,
@@ -17,6 +18,7 @@ export type {
Course,
Enrollment,
Day,
+ UserWithCoursesAndSubjects,
MessageID,
ShareRoomID,
Message,
diff --git a/common/zod/schemas.ts b/common/zod/schemas.ts
index 1c114305..52411ac6 100644
--- a/common/zod/schemas.ts
+++ b/common/zod/schemas.ts
@@ -38,6 +38,11 @@ export const InterestSubjectSchema = z.object({
group: z.string(),
});
+export const InterestSchema = z.object({
+ userId: UserIDSchema,
+ subjectId: z.number(),
+});
+
export const UserSchema = z.object({
id: UserIDSchema,
guid: GUIDSchema,
@@ -103,6 +108,11 @@ export const EnrollmentSchema = z.object({
courseId: CourseIDSchema,
});
+export const UserWithCoursesAndSubjectsSchema = UserSchema.extend({
+ courses: CourseSchema.array(),
+ interestSubjects: InterestSubjectSchema.array(),
+});
+
export const MessageIDSchema = z.number(); // TODO! Add __internal_prevent_cast_MessageID: PhantomData
export const ShareRoomIDSchema = z.number();
diff --git a/common/zod/types.ts b/common/zod/types.ts
index c0f1a44b..68714005 100644
--- a/common/zod/types.ts
+++ b/common/zod/types.ts
@@ -14,6 +14,7 @@ import type {
InitRoomSchema,
InitSharedRoomSchema,
InitUserSchema,
+ InterestSchema,
InterestSubjectSchema,
IntroLongSchema,
IntroShortSchema,
@@ -37,6 +38,7 @@ import type {
UpdateUserSchema,
UserIDSchema,
UserSchema,
+ UserWithCoursesAndSubjectsSchema,
} from "./schemas";
export type UserID = z.infer;
@@ -47,6 +49,7 @@ export type PictureUrl = z.infer;
export type Gender = z.infer;
export type RelationshipStatus = z.infer;
export type InterestSubject = z.infer;
+export type Interest = z.infer;
export type User = z.infer;
export type InitUser = z.infer;
export type UpdateUser = z.infer;
@@ -59,6 +62,9 @@ export type Course = z.infer;
export type Enrollment = z.infer;
export type Day = z.infer;
export type Period = z.infer;
+export type UserWithCoursesAndSubjects = z.infer<
+ typeof UserWithCoursesAndSubjectsSchema
+>;
export type MessageID = z.infer;
export type ShareRoomID = z.infer;
export type Message = z.infer;
diff --git a/server/src/database/requests.ts b/server/src/database/requests.ts
index e1e2efaa..0b829093 100644
--- a/server/src/database/requests.ts
+++ b/server/src/database/requests.ts
@@ -1,5 +1,9 @@
import { Err, Ok, type Result } from "common/lib/result";
-import type { Relationship, User, UserID } from "common/types";
+import type {
+ Relationship,
+ UserID,
+ UserWithCoursesAndSubjects,
+} from "common/types";
import { prisma } from "./client";
// マッチリクエストの送信
@@ -114,7 +118,7 @@ export async function cancelRequest(
//ユーザーへのリクエストを探す 俺をリクエストしているのは誰だ
export async function getPendingRequestsToUser(
userId: UserID,
-): Promise> {
+): Promise> {
try {
const found = await prisma.user.findMany({
where: {
@@ -125,8 +129,37 @@ export async function getPendingRequestsToUser(
},
},
},
+ include: {
+ enrollments: {
+ include: {
+ course: {
+ include: {
+ enrollments: true,
+ slots: true,
+ },
+ },
+ },
+ },
+ interests: {
+ include: {
+ subject: true,
+ },
+ },
+ },
});
- return Ok(found);
+ return Ok(
+ found.map((user) => {
+ return {
+ ...user,
+ interestSubjects: user.interests.map((interest) => {
+ return interest.subject;
+ }),
+ courses: user.enrollments.map((enrollment) => {
+ return enrollment.course;
+ }),
+ };
+ }),
+ );
} catch (e) {
return Err(e);
}
@@ -135,7 +168,7 @@ export async function getPendingRequestsToUser(
//ユーザーがリクエストしている人を探す 俺がリクエストしているのは誰だ
export async function getPendingRequestsFromUser(
userId: UserID,
-): Promise> {
+): Promise> {
try {
const found = await prisma.user.findMany({
where: {
@@ -146,15 +179,46 @@ export async function getPendingRequestsFromUser(
},
},
},
+ include: {
+ enrollments: {
+ include: {
+ course: {
+ include: {
+ enrollments: true,
+ slots: true,
+ },
+ },
+ },
+ },
+ interests: {
+ include: {
+ subject: true,
+ },
+ },
+ },
});
- return Ok(found);
+ return Ok(
+ found.map((user) => {
+ return {
+ ...user,
+ interestSubjects: user.interests.map((interest) => {
+ return interest.subject;
+ }),
+ courses: user.enrollments.map((enrollment) => {
+ return enrollment.course;
+ }),
+ };
+ }),
+ );
} catch (e) {
return Err(e);
}
}
//マッチした人の取得
-export async function getMatchedUser(userId: UserID): Promise> {
+export async function getMatchedUser(
+ userId: UserID,
+): Promise> {
try {
const found = await prisma.user.findMany({
where: {
@@ -177,8 +241,37 @@ export async function getMatchedUser(userId: UserID): Promise> {
},
],
},
+ include: {
+ enrollments: {
+ include: {
+ course: {
+ include: {
+ enrollments: true,
+ slots: true,
+ },
+ },
+ },
+ },
+ interests: {
+ include: {
+ subject: true,
+ },
+ },
+ },
});
- return Ok(found);
+ return Ok(
+ found.map((user) => {
+ return {
+ ...user,
+ interestSubjects: user.interests.map((interest) => {
+ return interest.subject;
+ }),
+ courses: user.enrollments.map((enrollment) => {
+ return enrollment.course;
+ }),
+ };
+ }),
+ );
} catch (e) {
return Err(e);
}
diff --git a/server/src/database/users.ts b/server/src/database/users.ts
index 8ba7b9a1..3d008bd3 100644
--- a/server/src/database/users.ts
+++ b/server/src/database/users.ts
@@ -1,5 +1,13 @@
import { Err, Ok, type Result } from "common/lib/result";
-import type { GUID, UpdateUser, User, UserID } from "common/types";
+import type {
+ Course,
+ GUID,
+ InterestSubject,
+ UpdateUser,
+ User,
+ UserID,
+ UserWithCoursesAndSubjects,
+} from "common/types";
import { prisma } from "./client";
// ユーザーの作成
@@ -17,15 +25,42 @@ export async function createUser(
}
// ユーザーの取得
-export async function getUser(guid: GUID): Promise> {
+export async function getUser(
+ guid: GUID,
+): Promise> {
try {
const user = await prisma.user.findUnique({
where: {
guid: guid,
},
+ include: {
+ enrollments: {
+ include: {
+ course: {
+ include: {
+ enrollments: true,
+ slots: true,
+ },
+ },
+ },
+ },
+ interests: {
+ include: {
+ subject: true,
+ },
+ },
+ },
});
if (!user) return Err(404);
- return Ok(user);
+ return Ok({
+ ...user,
+ interestSubjects: user.interests.map((interest) => {
+ return interest.subject;
+ }),
+ courses: user.enrollments.map((enrollment) => {
+ return enrollment.course;
+ }),
+ });
} catch (e) {
return Err(e);
}
@@ -50,14 +85,42 @@ export async function getUserIDByGUID(guid: GUID): Promise> {
.catch((err) => Err(err));
}
-export async function getUserByID(id: UserID): Promise> {
+export async function getUserByID(
+ id: UserID,
+): Promise> {
try {
const user = await prisma.user.findUnique({
where: {
id,
},
+ include: {
+ enrollments: {
+ include: {
+ course: {
+ include: {
+ enrollments: true,
+ slots: true,
+ },
+ },
+ },
+ },
+ interests: {
+ include: {
+ subject: true,
+ },
+ },
+ },
+ });
+ if (!user) return Err(404);
+ return Ok({
+ ...user,
+ interestSubjects: user.interests.map((interest) => {
+ return interest.subject;
+ }),
+ courses: user.enrollments.map((enrollment) => {
+ return enrollment.course;
+ }),
});
- return user === null ? Err(404) : Ok(user);
} catch (e) {
return Err(e);
}
@@ -100,10 +163,42 @@ export async function deleteUser(userId: UserID): Promise> {
}
// ユーザーの全取得
-export async function getAllUsers(): Promise> {
+export async function getAllUsers(): Promise<
+ Result<(User & { courses: Course[]; interestSubjects: InterestSubject[] })[]>
+> {
try {
- const users = await prisma.user.findMany();
- return Ok(users);
+ const users = await prisma.user.findMany({
+ include: {
+ enrollments: {
+ include: {
+ course: {
+ include: {
+ enrollments: true,
+ slots: true,
+ },
+ },
+ },
+ },
+ interests: {
+ include: {
+ subject: true,
+ },
+ },
+ },
+ });
+ return Ok(
+ users.map((user) => {
+ return {
+ ...user,
+ interestSubjects: user.interests.map((interest) => {
+ return interest.subject;
+ }),
+ courses: user.enrollments.map((enrollment) => {
+ return enrollment.course;
+ }),
+ };
+ }),
+ );
} catch (e) {
return Err(e);
}
diff --git a/server/src/functions/engines/recommendation.ts b/server/src/functions/engines/recommendation.ts
index 76850d01..cac0bcd4 100644
--- a/server/src/functions/engines/recommendation.ts
+++ b/server/src/functions/engines/recommendation.ts
@@ -1,6 +1,6 @@
import { recommend as sql } from "@prisma/client/sql";
import { Err, Ok, type Result } from "common/lib/result";
-import type { User, UserID } from "common/types";
+import type { UserID, UserWithCoursesAndSubjects } from "common/types";
import { prisma } from "../../database/client";
import { getUserByID } from "../../database/users";
@@ -8,7 +8,14 @@ export async function recommendedTo(
user: UserID,
limit: number,
offset: number,
-): Promise>> {
+): Promise<
+ Result<
+ Array<{
+ u: UserWithCoursesAndSubjects;
+ count: number;
+ }>
+ >
+> {
try {
const result = await prisma.$queryRawTyped(sql(user, limit, offset));
return Promise.all(
diff --git a/server/src/functions/user.ts b/server/src/functions/user.ts
index 6a1a7644..228edc6e 100644
--- a/server/src/functions/user.ts
+++ b/server/src/functions/user.ts
@@ -1,5 +1,9 @@
-import { Result } from "common/lib/result";
-import type { GUID, User, UserID } from "common/types";
+import type {
+ GUID,
+ User,
+ UserID,
+ UserWithCoursesAndSubjects,
+} from "common/types";
import { getMatchedUser } from "../database/requests";
import * as db from "../database/users";
import * as http from "./share/http";
@@ -13,7 +17,9 @@ export async function getAllUsers(): Promise> {
return http.ok(users.value);
}
-export async function getUser(guid: GUID): Promise> {
+export async function getUser(
+ guid: GUID,
+): Promise> {
const user = await db.getUser(guid);
if (!user.ok) {
if (user.error === 404) return http.notFound();
@@ -42,7 +48,9 @@ export async function userExists(guid: GUID): Promise> {
return http.internalError("db error");
}
-export async function getMatched(user: UserID): Promise> {
+export async function getMatched(
+ user: UserID,
+): Promise> {
const matchedUsers = await getMatchedUser(user);
if (!matchedUsers.ok) return http.internalError();
diff --git a/server/src/router/users.ts b/server/src/router/users.ts
index 5be8fcf8..87c6586e 100644
--- a/server/src/router/users.ts
+++ b/server/src/router/users.ts
@@ -1,4 +1,8 @@
-import type { GUID, UpdateUser } from "common/types";
+import type {
+ GUID,
+ UpdateUser,
+ UserWithCoursesAndSubjects,
+} from "common/types";
import type { User } from "common/types";
import {
GUIDSchema,
@@ -106,7 +110,7 @@ router.get("/guid/:guid", async (req: Request, res: Response) => {
if (!user.ok) {
return res.status(404).json({ error: "User not found" });
}
- const json: User = user.value;
+ const json: UserWithCoursesAndSubjects = user.value;
res.status(200).json(json);
});
diff --git a/web/api/internal/endpoints.ts b/web/api/internal/endpoints.ts
index 2ebc350a..e087c87f 100644
--- a/web/api/internal/endpoints.ts
+++ b/web/api/internal/endpoints.ts
@@ -12,7 +12,7 @@ export type UserID = number;
* GET -> get user's info. TODO: filter return info by user's options and open level.
* - statuses:
* - 200: ok.
- * - body: User
+ * - body: UserWithCoursesAndSubjects
* - 400: not found.
* - 500: internal error.
**/
@@ -43,7 +43,7 @@ export const users = `${origin}/users`;
* GET -> get top N users recommended to me.
* - statuses:
* - 200: good.
- * - body: User[]
+ * - body: UserWithCoursesAndSubjects[]
* - 401: auth error.
* - 500: internal error
**/
@@ -62,7 +62,7 @@ export const recommendedUsers = `${origin}/users/recommended`;
* - request body: Omit
* - statuses:
* - 200: ok.
- * - body: User
+ * - body: UserWithCoursesAndSubjects
* - 500: internal error.
*
* [v] 実装済み
@@ -79,7 +79,7 @@ export const me = `${origin}/users/me`;
* GET -> list all matched users.
* - statuses:
* - 200: ok.
- * - body: User[]
+ * - body: UserWithCoursesAndSubjects[]
* - 401: unauthorized.
* - 500: internal error.
**/
@@ -90,7 +90,7 @@ export const matchedUsers = `${origin}/users/matched`;
* GET -> list all users that sent request to you.
* - statuses:
* - 200: ok.
- * - body: User[]
+ * - body: UserWithCoursesAndSubjects[]
* - 401: unauthorized.
* - 500: internal error.
**/
@@ -101,7 +101,7 @@ export const pendingRequestsToMe = `${origin}/users/pending/to-me`;
* GET -> list all users that you sent request.
* - statuses:
* - 200: ok.
- * - body: User[]
+ * - body: UserWithCoursesAndSubjects[]
* - 401: unauthorized.
* - 500: internal error.
**/
@@ -112,7 +112,7 @@ export const pendingRequestsFromMe = `${origin}/users/pending/from-me`;
* GET -> get user's info. TODO: filter return info by user's options and open level.
* - statuses:
* - 200: ok.
- * - body: User
+ * - body: UserWithCoursesAndSubjects
* - 400: not found.
* - 500: internal error.
**/
@@ -126,6 +126,7 @@ export const userByGUID = (guid: GUID) => {
* GET -> check if the user exists.
* - statuses:
* - 200: yes, user exists.
+ * - body: UserWithCoursesAndSubjects
* - 404: no, user doesn't exist.
* - 500: internal error.
**/
diff --git a/web/api/user.ts b/web/api/user.ts
index add3efcb..0f535341 100644
--- a/web/api/user.ts
+++ b/web/api/user.ts
@@ -1,6 +1,15 @@
-import type { GUID, UpdateUser, User, UserID } from "common/types.ts";
+import type {
+ GUID,
+ UpdateUser,
+ User,
+ UserID,
+ UserWithCoursesAndSubjects,
+} from "common/types.ts";
import { parseUser } from "common/zod/methods.ts";
-import { UserIDSchema, UserSchema } from "common/zod/schemas.ts";
+import {
+ UserIDSchema,
+ UserWithCoursesAndSubjectsSchema,
+} from "common/zod/schemas.ts";
import { z } from "zod";
import { credFetch } from "~/firebase/auth/lib.ts";
import { type Hook, useCustomizedSWR } from "~/hooks/useCustomizedSWR.ts";
@@ -8,22 +17,22 @@ import { useAuthorizedData } from "~/hooks/useData.ts";
import endpoints from "./internal/endpoints.ts";
import type { Hook as UseHook } from "./share/types.ts";
-const UserListSchema = z.array(UserSchema);
+const UserListSchema = z.array(UserWithCoursesAndSubjectsSchema);
-export function useAll(): Hook {
+export function useAll(): Hook {
return useCustomizedSWR("users::all", all, UserListSchema);
}
-export function useRecommended(): UseHook {
+export function useRecommended(): UseHook {
const url = endpoints.recommendedUsers;
- return useAuthorizedData(url);
+ return useAuthorizedData(url);
}
-export function useMatched(): Hook {
+export function useMatched(): Hook {
return useCustomizedSWR("users::matched", matched, UserListSchema);
}
-export function usePendingToMe(): Hook {
+export function usePendingToMe(): Hook {
return useCustomizedSWR("users::pending::to-me", pendingToMe, UserListSchema);
}
-export function usePendingFromMe(): Hook {
+export function usePendingFromMe(): Hook {
return useCustomizedSWR(
"users::pending::from-me",
pendingFromMe,
@@ -31,30 +40,34 @@ export function usePendingFromMe(): Hook {
);
}
-async function all(): Promise {
+async function all(): Promise {
const res = await credFetch("GET", endpoints.users);
return res.json();
}
-async function matched(): Promise {
+async function matched(): Promise {
const res = await credFetch("GET", endpoints.matchedUsers);
return res.json();
}
-async function pendingToMe(): Promise {
+async function pendingToMe(): Promise {
const res = await credFetch("GET", endpoints.pendingRequestsToMe);
return await res.json();
}
-async function pendingFromMe(): Promise {
+async function pendingFromMe(): Promise {
const res = await credFetch("GET", endpoints.pendingRequestsFromMe);
return await res.json();
}
// 自身のユーザー情報を取得する
-export function useAboutMe(): Hook {
- return useCustomizedSWR("users::aboutMe", aboutMe, UserSchema);
+export function useAboutMe(): Hook {
+ return useCustomizedSWR(
+ "users::aboutMe",
+ aboutMe,
+ UserWithCoursesAndSubjectsSchema,
+ );
}
-async function aboutMe(): Promise {
+async function aboutMe(): Promise {
const res = await credFetch("GET", endpoints.me);
return res.json();
}
diff --git a/web/app/home/page.tsx b/web/app/home/page.tsx
index aedd1545..f489da5f 100644
--- a/web/app/home/page.tsx
+++ b/web/app/home/page.tsx
@@ -1,7 +1,7 @@
"use client";
import CloseIcon from "@mui/icons-material/Close";
-import type { User } from "common/types";
+import type { UserWithCoursesAndSubjects } from "common/types";
import { motion, useAnimation } from "framer-motion";
import { useCallback, useEffect, useState } from "react";
import { MdThumbUp } from "react-icons/md";
@@ -21,9 +21,9 @@ export default function Home() {
} = useMyID();
const [_, rerender] = useState({});
- const [recommended, setRecommended] = useState>(
- () => new Queue([]),
- );
+ const [recommended, setRecommended] = useState<
+ Queue
+ >(() => new Queue([]));
useEffect(() => {
if (data) setRecommended(new Queue(data));
}, [data]);
diff --git a/web/components/Card.tsx b/web/components/Card.tsx
index 47c889c7..2ba27e57 100644
--- a/web/components/Card.tsx
+++ b/web/components/Card.tsx
@@ -1,12 +1,12 @@
import ThreeSixtyIcon from "@mui/icons-material/ThreeSixty";
import { Chip } from "@mui/material";
-import type { User, UserID } from "common/types";
+import type { UserID, UserWithCoursesAndSubjects } from "common/types";
import { useState } from "react";
import NonEditableCoursesTable from "./course/NonEditableCoursesTable";
import UserAvatar from "./human/avatar";
interface CardProps {
- displayedUser: User;
+ displayedUser: UserWithCoursesAndSubjects;
comparisonUserId?: UserID;
onFlip?: (isBack: boolean) => void;
}
@@ -97,6 +97,12 @@ const CardFront = ({ displayedUser }: CardProps) => {
{displayedUser.intro}
+ TODO: これはサンプルです
+
+ {displayedUser.interestSubjects.map((subject) => (
+ - {subject.name}
+ ))}
+
diff --git a/web/components/DraggableCard.tsx b/web/components/DraggableCard.tsx
index 130d5a5e..d6ae570d 100644
--- a/web/components/DraggableCard.tsx
+++ b/web/components/DraggableCard.tsx
@@ -1,6 +1,6 @@
import CloseIcon from "@mui/icons-material/Close";
import { Box, Typography } from "@mui/material";
-import type { User, UserID } from "common/types";
+import type { UserID, UserWithCoursesAndSubjects } from "common/types";
import { motion, useMotionValue, useMotionValueEvent } from "framer-motion";
import { useCallback, useState } from "react";
import { MdThumbUp } from "react-icons/md";
@@ -9,7 +9,7 @@ import { Card } from "./Card";
const SWIPE_THRESHOLD = 30;
interface DraggableCardProps {
- displayedUser: User;
+ displayedUser: UserWithCoursesAndSubjects;
comparisonUserId?: UserID;
onSwipeRight: () => void;
onSwipeLeft: () => void;
diff --git a/web/components/common/modal/ModalProvider.tsx b/web/components/common/modal/ModalProvider.tsx
index 788369f1..37fe8836 100644
--- a/web/components/common/modal/ModalProvider.tsx
+++ b/web/components/common/modal/ModalProvider.tsx
@@ -1,4 +1,4 @@
-import type { User } from "common/types";
+import type { UserWithCoursesAndSubjects } from "common/types";
import { type ReactNode, createContext, useContext, useState } from "react";
import { useMyID } from "~/api/user";
import { Card } from "../../Card";
@@ -6,7 +6,7 @@ import { Card } from "../../Card";
const ModalContext = createContext(undefined);
type ModalContextProps = {
- openModal: (user: User) => void;
+ openModal: (user: UserWithCoursesAndSubjects) => void;
closeModal: () => void;
};
@@ -16,12 +16,13 @@ type ModalProviderProps = {
export const ModalProvider = ({ children }: ModalProviderProps) => {
const [open, setOpen] = useState(false);
- const [selectedUser, setSelectedUser] = useState(null);
+ const [selectedUser, setSelectedUser] =
+ useState(null);
const {
state: { data: myId },
} = useMyID();
- const openModal = (user: User) => {
+ const openModal = (user: UserWithCoursesAndSubjects) => {
setSelectedUser(user);
setOpen(true);
};