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
4 changes: 2 additions & 2 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@

"@types/body-parser": ["@types/body-parser@1.19.5", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg=="],

"@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="],
"@types/bun": ["@types/bun@1.2.9", "", { "dependencies": { "bun-types": "1.2.9" } }, "sha512-epShhLGQYc4Bv/aceHbmBhOz1XgUnuTZgcxjxk+WXwNyDXavv5QHD1QEFV0FwbTSQtNq6g4ZcV6y0vZakTjswg=="],

"@types/caseless": ["@types/caseless@0.12.5", "", {}, "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg=="],

Expand Down Expand Up @@ -536,7 +536,7 @@

"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],

"bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="],
"bun-types": ["bun-types@1.2.9", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-dk/kOEfQbajENN/D6FyiSgOKEuUi9PWfqKQJEgwKrCMWbjS/S6tEXp178mWvWAcUSYm9ArDlWHZKO3T/4cLXiw=="],

"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],

Expand Down
8 changes: 5 additions & 3 deletions common/lib/panic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// unexpected error
export function panic(reason: string): never {
throw new Error(reason, {
cause: "panic",
});
throw new Error(reason);
// TODO: 型エラーとなるため一時的にコメントアウト
// throw new Error(reason, {
// cause: "panic",
// });
}
51 changes: 50 additions & 1 deletion server/prisma/sql/recommend.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
-- $1 = senderId
SELECT
*,
recv.id,
recv.name,
recv.gender,
recv.grade,
recv.faculty,
recv.department,
recv.intro,
recv."guid",
recv."pictureUrl",
json_agg(DISTINCT jsonb_build_object(
'id', c.id,
'name', c.name,
'teacher', c.teacher,
'slots', (
SELECT json_agg(
jsonb_build_object(
'courseId', "Slot"."courseId",
'day', "Slot"."day",
'period', "Slot"."period"
)
) FROM "Slot" WHERE "Slot"."courseId" = c.id)
)
) AS "courses",
json_agg(DISTINCT jsonb_build_object(
'id', s.id,
'name', s.name,
'group', s.group
)) AS "interestSubjects",
-- course overlap
(SELECT COUNT(1) FROM "Course" course
WHERE EXISTS (SELECT 1 FROM "Enrollment" e WHERE e."courseId" = course.id AND e."userId" = recv.id)
Expand All @@ -12,6 +39,13 @@ SELECT
AND EXISTS (SELECT 1 FROM "Interest" i WHERE i."subjectId" = subj.id AND i."userId" = $1)
) AS overlap
FROM "User" recv

INNER JOIN "Enrollment" ON "Enrollment"."userId" = recv.id
INNER JOIN "Course" c on c.id = "Enrollment"."courseId"
INNER JOIN "Slot" ON "Slot"."courseId" = c.id
INNER JOIN "Interest" ON "Interest"."userId" = recv.id
INNER JOIN "InterestSubject" s ON s.id = "Interest"."subjectId"

WHERE recv.id <> $1

AND NOT EXISTS (
Expand All @@ -26,5 +60,20 @@ AND NOT EXISTS (
AND status = 'PENDING'
)

-- 授業の登録も興味分野の登録も 0 件のユーザは除外
AND (
EXISTS (
SELECT 1 FROM "Enrollment" e
WHERE e."userId" = recv.id
)
OR
EXISTS (
SELECT 1 FROM "Interest" i
WHERE i."userId" = recv.id
)
)

GROUP BY recv.id

ORDER BY overlap DESC
LIMIT $2 OFFSET $3;
41 changes: 18 additions & 23 deletions server/src/functions/engines/recommendation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { recommend } from "@prisma/client/sql";
import type { UserID, UserWithCoursesAndSubjects } from "common/types";
import type {
Course,
InterestSubject,
UserID,
UserWithCoursesAndSubjects,
} from "common/types";
import { prisma } from "../../database/client";
import { getCoursesByUserId } from "../../database/courses";
import * as interest from "../../database/interest";
import { getUserByID } from "../../database/users";

export async function recommendedTo(
user: UserID,
Expand All @@ -15,23 +17,16 @@ export async function recommendedTo(
count: number;
}>
> {
const result = await prisma.$queryRawTyped(recommend(user, limit, offset));
return Promise.all(
result.map(async (res) => {
const { overlap: count, ...u } = res;
if (count === null) throw new Error("count is null: something is wrong");
// TODO: user の情報はここで再度 DB に問い合わせるのではなく、 recommend の sql で取得
const user = await getUserByID(u.id);
const courses = getCoursesByUserId(u.id);
const subjects = interest.of(u.id);
return {
count: Number(count),
u: {
...user,
courses: await courses,
interestSubjects: await subjects,
},
};
}),
);
const users = await prisma.$queryRawTyped(recommend(user, limit, offset));
return users.map((user) => {
const { overlap: count, ...u } = user;
return {
count: Number(count),
u: {
...u,
interestSubjects: u.interestSubjects as InterestSubject[], // TODO: type
courses: u.courses as Course[], // TODO: type
},
};
});
}
4 changes: 3 additions & 1 deletion web/app/chat/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<ModalProvider>
<Header title="チャット" />
<NavigateByAuthState type="toLoginForUnauthenticated">
<div className="h-full overflow-y-auto pt-12 pb-12">{children}</div>
<div className="cm-pb-footer h-full overflow-y-auto pt-12">
{children}
</div>
</NavigateByAuthState>
<BottomBar activeTab="3_chat" />
</ModalProvider>
Expand Down
4 changes: 3 additions & 1 deletion web/app/friends/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<ModalProvider>
<Header title="フレンド" />
<NavigateByAuthState type="toLoginForUnauthenticated">
<div className="h-full overflow-y-auto pt-12 pb-12">{children}</div>
<div className="cm-pb-footer h-full overflow-y-auto pt-12">
{children}
</div>
</NavigateByAuthState>
<BottomBar activeTab="1_friends" />
</ModalProvider>
Expand Down
5 changes: 5 additions & 0 deletions web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@
.cm-li-btn {
@apply no-animation h-auto w-full justify-start rounded-none border-none bg-white px-6 py-4 text-left font-normal text-base shadow-none hover:bg-zinc-100 focus:bg-zinc-300;
}

/* Bottom Bar の分の幅 */
.cm-pb-footer {
padding-bottom: calc(3rem + env(safe-area-inset-bottom));
}
2 changes: 1 addition & 1 deletion web/app/home/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function Layout({
<>
<Header />
<NavigateByAuthState type="toLoginForUnauthenticated">
<div className="h-full pt-12 pb-12">{children}</div>
<div className="cm-pb-footer h-full pt-12">{children}</div>
</NavigateByAuthState>
<BottomBar activeTab="0_home" />
</>
Expand Down
62 changes: 15 additions & 47 deletions web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,30 @@
"use client";

import { CssBaseline, ThemeProvider, createTheme } from "@mui/material";
import { SnackbarProvider } from "notistack";
import React from "react";
import type React from "react";
import "./globals.css";
import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
import BanLandscape from "~/components/BanLandscape";
import SSEProvider from "~/components/SSEProvider";
import { AlertProvider } from "~/components/common/alert/AlertProvider";
import type { Metadata, Viewport } from "next";
import Providers from "~/components/Providers";

export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
viewportFit: "cover",
};

const theme = createTheme({
palette: {
primary: {
main: "#039BE5",
},
secondary: {
main: "#E9F8FF",
},
},
});
export const metadata: Metadata = {
title: "CourseMate (β版)",
description: "同じ授業の人と友達になろう",
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
}: { children: React.ReactNode }) {
return (
<html lang="en" className="h-full">
<head>
<meta charSet="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/course-mate-icon.svg" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
/>
<title>CourseMate</title>
</head>
<body className="h-full">
<ThemeProvider theme={theme}>
<CssBaseline />
<SnackbarProvider
autoHideDuration={2000}
anchorOrigin={{ horizontal: "right", vertical: "top" }}
>
<React.StrictMode>
<CssBaseline />
<AlertProvider>
{/* <ModalProvider> */}
<BanLandscape />
<SSEProvider>{children}</SSEProvider>
{/* </ModalProvider> */}
</AlertProvider>
</React.StrictMode>
</SnackbarProvider>
</ThemeProvider>
<Providers>{children}</Providers>
</body>
</html>
);
Expand Down
25 changes: 25 additions & 0 deletions web/app/manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { MetadataRoute } from "next";

export default function manifest(): MetadataRoute.Manifest {
return {
name: "CourseMate",
short_name: "CourseMate",
description: "同じ授業を履修している友達を見つけられるアプリ",
start_url: "/",
display: "standalone",
background_color: "#ffffff",
theme_color: "#ffffff",
icons: [
{
src: "/icon-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "/icon-512x512.png",
sizes: "512x512",
type: "image/png",
},
],
};
}
4 changes: 3 additions & 1 deletion web/app/search/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<ModalProvider>
<Header title="検索" />
<NavigateByAuthState type="toLoginForUnauthenticated">
<div className="h-full overflow-y-auto pt-12 pb-12">{children}</div>
<div className="cm-pb-footer h-full overflow-y-auto pt-12">
{children}
</div>
</NavigateByAuthState>
<BottomBar activeTab="2_search" />
</ModalProvider>
Expand Down
4 changes: 3 additions & 1 deletion web/app/settings/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export default function Layout({
<>
<Header title="設定" />
<NavigateByAuthState type="toLoginForUnauthenticated">
<div className="h-full overflow-y-auto pt-12 pb-12">{children}</div>
<div className="cm-pb-footer h-full overflow-y-auto pt-12">
{children}
</div>
</NavigateByAuthState>
<BottomBar activeTab="4_settings" />
</>
Expand Down
8 changes: 7 additions & 1 deletion web/components/BottomBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ function BottomBarCell({
export default function BottomBar(props: Props) {
const { activeTab } = props;
return (
<div className="fixed bottom-0 z-30 flex h-12 w-full flex-row items-center justify-around border-gray-200 border-t bg-white">
<div
className="fixed bottom-0 z-30 flex w-full flex-row items-center justify-around border-gray-200 border-t bg-white"
style={{
height: "calc(3rem + env(safe-area-inset-bottom))",
paddingBottom: "env(safe-area-inset-bottom)",
}}
>
<BottomBarCell
href="/home"
iconComponent={
Expand Down
37 changes: 27 additions & 10 deletions web/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type Props = {
};

export default function Header(props: Props) {
const { title, info } = props;
const { title } = props;
return (
<header className="fixed top-0 z-30 flex h-12 w-full items-center justify-center border-gray-200 border-b bg-white">
{title && (
Expand All @@ -20,23 +20,40 @@ export default function Header(props: Props) {
<CourseMateIcon width="30px" height="30px" />
</Link>
)}

{title ? (
<h1 className="w-full flex-grow text-center text-black text-xl">
{title}
</h1>
) : (
<CourseMateIcon width="30px" height="30px" />
)}
{info && (
<Link
href="/faq"
passHref
className="-translate-y-1/2 absolute top-1/2 right-3 transform"
>
<div className="dropdown dropdown-end -translate-y-1/2 absolute top-1/2 right-3 transform">
<button tabIndex={0} className="btn btn-ghost btn-circle" type="button">
<MdInfoOutline size={28} className="text-gray-500" />
</Link>
)}
</button>
<div className="dropdown-content z-[1] w-56 rounded-box bg-base-100 p-2 shadow">
<p className="p-2 text-xs">CourseMate は現在ベータ版です。</p>
<ul className="menu p-0">
<li>
<a
href="https://forms.gle/WvFTbsJoHjGp9Qt88"
target="_blank"
rel="noreferrer"
>
ご意見・バグ報告
</a>
</li>
{/* <li
<a>開発に参加する</a>
</li> */}
<li>
<Link href="/faq" passHref>
よくある質問
</Link>
</li>
</ul>
</div>
</div>
</header>
);
}
Loading
Loading