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
46 changes: 46 additions & 0 deletions .github/workflows/Tagger.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: npm versioner

on:
workflow_dispatch:
inputs:
version:
description: "Version to bump to"
required: true
default: "patch"
type: choice
options:
- "major"
- "minor"
- "patch"
prefix:
description: "Prefix for the pre-version"
required: false
type: choice
options:
- "alpha"
- "rc"

permissions:
id-token: write
contents: write

jobs:
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Bump version(Normal)
if: ${{ github.event.inputs.prefix == null }}
run: npm version ${{ github.event.inputs.version }}
- name: Bump version(Pre-release)
if: ${{ github.event.inputs.prefix != null }}
run: npm version pre${{ github.event.inputs.version }} --preid=${{ github.event.inputs.prefix }}
- run: |
git config user.name "actions-user"
git config user.email "action@github.com"
- name: Push changes
run: |
git push --follow-tags
git push
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 2 additions & 0 deletions .github/workflows/VercelPreview.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ on:
pull_request:
branches:
- main
- sandbox
- dev
jobs:
checkLabels:
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ on:
push:
branches:
- main
- sandbox
- dev
pull_request:
branches:
- main
- sandbox
- dev
jobs:
lint:
runs-on: ubuntu-latest
Expand Down
2 changes: 0 additions & 2 deletions src/app/api/clubs/recent/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@ const endpoint = process.env.DB_API_ENDPOINT;
export const GET = async () => {
const session = await auth();
const apiKey = (await headers()).get("X-Api-Key") as string;
console.log(`RT: ${apiKey}`);
const email = crypto.AES.decrypt(apiKey, process.env.API_ROUTE_SECRET as string).toString(
crypto.enc.Utf8
);
console.log(`RT: ${email}`);
const apiCheck =
email &&
(email.endsWith("@nnn.ed.jp") || email.endsWith("@nnn.ac.jp") || email.endsWith("@n-jr.jp"));
Expand Down
14 changes: 13 additions & 1 deletion src/app/api/clubs/search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,25 @@
import { auth } from "@/auth";
import Club from "@/models/Club";
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto-js";
import { Session } from "next-auth";

const endpoint = process.env.DB_API_ENDPOINT;

export const GET = async (req: NextRequest) => {
const searchParams = req.nextUrl.searchParams;
const query = searchParams.get("query");
const session = await auth();
let session: boolean | Session | null = await auth();
if (!session) {
const headers = req.headers;
const apiKey = headers.get("X-Api-Key");
const email = crypto.AES.decrypt(
apiKey as string,
process.env.API_ROUTE_SECRET as string
).toString(crypto.enc.Utf8);
if (email.endsWith("@nnn.ed.jp") || email.endsWith("@nnn.ac.jp") || email.endsWith("@n-jr.jp"))
session = true;
}
const response = await fetch(
`${endpoint}/clubs?${query ? `&search=${query}` : ""}&order=created_at,desc`
);
Expand Down
42 changes: 40 additions & 2 deletions src/app/clubs/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@
import { auth } from "@/auth";
import ClubList from "@/components/ClubList";
import ClubSearchForm from "@/components/search/SearchBox";
import { Box, Stack, Typography } from "@mui/material";
import Club from "@/models/Club";
import { Box, CircularProgress, Stack, Typography } from "@mui/material";
import { Metadata } from "next";
import { headers } from "next/headers";
import { Suspense } from "react";
import CryptoJS from "crypto-js";

export const metadata: Metadata = {
title: "同好会一覧 - Linkle",
description: "Linkleに登録されている同好会一覧です。",
};

export default async function Home() {
const headersData = await headers();
const host = headersData.get("host");
const protocol =
headersData.get("x-forwarded-proto") ?? host?.startsWith("localhost") ? "http" : "https";
const cookie = headersData.get("cookie");
const sessionID = cookie?.split(";").find((c) => c.trim().startsWith("authjs.session-token"));
const apiBase = `${protocol}://${host}`;

const fetchData = new Promise<Club[]>(async (resolve, reject) => {
try {
const session = await auth();
const res = await fetch(`${apiBase}/api/clubs/search`, {
headers: {
"Content-Type": "application/json",
"X-Api-key": CryptoJS.AES.encrypt(
session?.user?.email ?? "No Auth",
process.env.API_ROUTE_SECRET as string
).toString(),
...(sessionID && { Cookie: sessionID }),
},
});
if (!res.ok) {
throw new Error("Failed to fetch data" + res.statusText);
}
resolve((await res.json()) as Club[]);
} catch (error) {
console.log(error);
reject("Failed to fetch data" + error);
}
});

return (
<Stack
px={{ xs: 2, lg: 0 }}
Expand Down Expand Up @@ -38,7 +74,9 @@ export default async function Home() {
>
<ClubSearchForm />
</Box>
<ClubList />
<Suspense fallback={<CircularProgress />}>
<ClubList fetchData={fetchData} />
</Suspense>
</Stack>
);
}
48 changes: 44 additions & 4 deletions src/app/search/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,50 @@
import { auth } from "@/auth";
import ClubSearchForm from "@/components/search/SearchBox";
import SearchResultsPage from "@/components/search/SearchView";
import SearchTitle from "@/components/search/SerachTitle";
import { Box, Stack } from "@mui/material";
import Club from "@/models/Club";
import { Box, CircularProgress, Stack } from "@mui/material";
import { Metadata } from "next";
import { headers } from "next/headers";
import { Suspense } from "react";
import CryptoJS from "crypto-js";

export const metadata: Metadata = {
title: "クラブ検索 - Linkle",
description: "Linkleのクラブ検索ページです。",
};

export default function Home() {
export default async function Home({ searchParams }: { searchParams: Promise<{ query: string }> }) {
const { query } = await searchParams;
const headersData = await headers();
const host = headersData.get("host");
const protocol =
headersData.get("x-forwarded-proto") ?? host?.startsWith("localhost") ? "http" : "https";
const cookie = headersData.get("cookie");
const sessionID = cookie?.split(";").find((c) => c.trim().startsWith("authjs.session-token"));
const apiBase = `${protocol}://${host}`;
const fetchData = new Promise<Club[] | string>(async (resolve, reject) => {
if (query) {
const session = await auth();
const res = await fetch(`${apiBase}/api/clubs/search?query=${query}`, {
headers: {
"Content-Type": "application/json",
"X-Api-key": CryptoJS.AES.encrypt(
session?.user?.email ?? "No Auth",
process.env.API_ROUTE_SECRET as string
).toString(),
...(sessionID && { Cookie: sessionID }),
},
});
if (res.ok) {
const data = await res.json();
resolve(data);
} else {
const error = await res.text();
reject(error);
}
} else resolve("queryが指定されていません。");
});
return (
<>
<Stack
Expand All @@ -24,9 +59,14 @@ export default function Home() {
width={{ xs: "100%", lg: 2 / 5 }}
sx={{ p: 5 }}
>
<ClubSearchForm />
<ClubSearchForm query={query} />
</Box>
<SearchResultsPage />
<Suspense fallback={<CircularProgress />}>
<SearchResultsPage
promise={fetchData}
query={query}
/>
</Suspense>
</Stack>
</>
);
Expand Down
71 changes: 18 additions & 53 deletions src/components/ClubList.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,32 @@
"use client";

import React, { Suspense } from "react";
import {
Typography,
CircularProgress,
Alert,
Grid2,
Pagination,
PaginationItem,
Stack,
} from "@mui/material";
import React, { use } from "react";
import { Typography, Alert, Grid2, Pagination, PaginationItem, Stack } from "@mui/material";
import { useSearchParams } from "next/navigation";
import Link from "next/link";
import ClubCard from "./ClubCard";
import Club from "@/models/Club";

const SearchResultsPage: React.FC = () => {
const ClubList = ({ fetchData }: { fetchData: Promise<Club[] | string> }) => {
const searchParams = useSearchParams();
const query = searchParams.get("query") || null;
const page = searchParams.get("page");

const [clubs, setSearchResult] = React.useState<Club[] | null>(null);
const [searchError, setSearchError] = React.useState<string | null>(null);
const [loading, setLoading] = React.useState(false);
const clubs = use(fetchData);

if (typeof clubs === "string") {
return (
<Stack
width={"100%"}
spacing={2}
justifyContent={"center"}
alignItems={"center"}
justifyItems={"center"}
>
<Alert severity="error">{clubs}</Alert>
</Stack>
);
}

React.useEffect(() => {
const fetchData = async () => {
setLoading(true);
setSearchError(null);
try {
const result = await (await fetch(`/api/clubs/search`)).json();
setSearchResult(result);
} catch (error) {
setSearchError("検索中にエラーが発生しました。もう一度お試しください。");
console.log(error);
} finally {
setLoading(false);
}
};
fetchData();
}, [query]);
return (
<Stack
width={"100%"}
Expand All @@ -56,22 +43,6 @@ const SearchResultsPage: React.FC = () => {
justifyContent="center"
width={"100%"}
>
{searchError && (
<Grid2 size={16}>
<Alert
severity="error"
style={{ marginTop: "20px" }}
>
{searchError}
</Alert>
</Grid2>
)}
{loading && (
<Grid2 size={16}>
<CircularProgress />
</Grid2>
)}

{clubs && clubs.length > 0 && (
<>
{clubs.map((club, index) => {
Expand Down Expand Up @@ -129,10 +100,4 @@ const SearchResultsPage: React.FC = () => {
);
};

const ClubList = () => (
<Suspense fallback={<CircularProgress />}>
<SearchResultsPage />
</Suspense>
);

export default ClubList;
6 changes: 3 additions & 3 deletions src/components/search/SearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { useRouter } from "next/navigation"; // Next.js のルーターを使用
import theme from "@/theme/primary";
import formTheme from "@/theme/form";

const ClubSearchForm: React.FC = () => {
const ClubSearchForm = ({ query }: { query?: string | undefined }) => {
const { control, handleSubmit } = useForm<{ query: string }>({
defaultValues: { query: "" },
defaultValues: { query: query },
});
const router = useRouter();

Expand All @@ -35,10 +35,10 @@ const ClubSearchForm: React.FC = () => {
render={({ field }) => (
<TextField
color="primary"
{...field}
label="同好会名"
variant="outlined"
fullWidth
{...field}
/>
)}
/>
Expand Down
Loading
Loading