aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--backend/api/handler.go4
-rw-r--r--backend/api/handler_wrapper.go35
-rw-r--r--backend/gen/api/handler_wrapper_gen.go15
-rw-r--r--frontend/app/App.tsx16
-rw-r--r--frontend/app/pages/DashboardPage.tsx54
-rw-r--r--frontend/app/pages/GolfWatchPage.tsx18
-rw-r--r--frontend/app/pages/IndexPage.tsx3
7 files changed, 72 insertions, 73 deletions
diff --git a/backend/api/handler.go b/backend/api/handler.go
index 57ae973..dcebfa1 100644
--- a/backend/api/handler.go
+++ b/backend/api/handler.go
@@ -218,7 +218,7 @@ func (h *Handler) GetGame(ctx context.Context, request GetGameRequestObject, use
}
return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
- if !row.IsPublic && !user.IsAdmin {
+ if !row.IsPublic && (user == nil || !user.IsAdmin) {
return GetGame404JSONResponse{
Message: "Game not found",
}, nil
@@ -318,7 +318,7 @@ func (h *Handler) GetGameWatchLatestStates(ctx context.Context, request GetGameW
Status: status,
}
- if row.UserID == user.UserID && !user.IsAdmin {
+ if user != nil && row.UserID == user.UserID && !user.IsAdmin {
return GetGameWatchLatestStates403JSONResponse{
Message: "You are one of the main players of this game",
}, nil
diff --git a/backend/api/handler_wrapper.go b/backend/api/handler_wrapper.go
index 48a0eef..1c8bc83 100644
--- a/backend/api/handler_wrapper.go
+++ b/backend/api/handler_wrapper.go
@@ -28,12 +28,7 @@ func NewHandler(queries db.Querier, txm db.TxManager, hub GameHubInterface, auth
}
func (h *HandlerWrapper) GetGame(ctx context.Context, request GetGameRequestObject) (GetGameResponseObject, error) {
- user, ok := GetUserFromContext(ctx)
- if !ok {
- return GetGame401JSONResponse{
- Message: "Unauthorized",
- }, nil
- }
+ user, _ := GetUserFromContext(ctx)
return h.impl.GetGame(ctx, request, user)
}
@@ -58,32 +53,17 @@ func (h *HandlerWrapper) GetGamePlaySubmissions(ctx context.Context, request Get
}
func (h *HandlerWrapper) GetGameWatchLatestStates(ctx context.Context, request GetGameWatchLatestStatesRequestObject) (GetGameWatchLatestStatesResponseObject, error) {
- user, ok := GetUserFromContext(ctx)
- if !ok {
- return GetGameWatchLatestStates401JSONResponse{
- Message: "Unauthorized",
- }, nil
- }
+ user, _ := GetUserFromContext(ctx)
return h.impl.GetGameWatchLatestStates(ctx, request, user)
}
func (h *HandlerWrapper) GetGameWatchRanking(ctx context.Context, request GetGameWatchRankingRequestObject) (GetGameWatchRankingResponseObject, error) {
- user, ok := GetUserFromContext(ctx)
- if !ok {
- return GetGameWatchRanking401JSONResponse{
- Message: "Unauthorized",
- }, nil
- }
+ user, _ := GetUserFromContext(ctx)
return h.impl.GetGameWatchRanking(ctx, request, user)
}
func (h *HandlerWrapper) GetGames(ctx context.Context, request GetGamesRequestObject) (GetGamesResponseObject, error) {
- user, ok := GetUserFromContext(ctx)
- if !ok {
- return GetGames401JSONResponse{
- Message: "Unauthorized",
- }, nil
- }
+ user, _ := GetUserFromContext(ctx)
return h.impl.GetGames(ctx, request, user)
}
@@ -98,12 +78,7 @@ func (h *HandlerWrapper) GetMe(ctx context.Context, request GetMeRequestObject)
}
func (h *HandlerWrapper) GetTournament(ctx context.Context, request GetTournamentRequestObject) (GetTournamentResponseObject, error) {
- user, ok := GetUserFromContext(ctx)
- if !ok {
- return GetTournament401JSONResponse{
- Message: "Unauthorized",
- }, nil
- }
+ user, _ := GetUserFromContext(ctx)
return h.impl.GetTournament(ctx, request, user)
}
diff --git a/backend/gen/api/handler_wrapper_gen.go b/backend/gen/api/handler_wrapper_gen.go
index e3e56c1..88f55c6 100644
--- a/backend/gen/api/handler_wrapper_gen.go
+++ b/backend/gen/api/handler_wrapper_gen.go
@@ -61,16 +61,26 @@ func main() {
}
slices.Sort(methods)
+ loginOptionalMethods := map[string]bool{
+ "GetGames": true,
+ "GetGame": true,
+ "GetGameWatchLatestStates": true,
+ "GetGameWatchRanking": true,
+ "GetTournament": true,
+ }
+
type TemplateParameter struct {
Name string
RequiresLogin bool
+ LoginOptional bool
RequiresAdminRole bool
}
templateParameters := make([]TemplateParameter, len(methods))
for i, method := range methods {
templateParameters[i] = TemplateParameter{
Name: method,
- RequiresLogin: method != "PostLogin",
+ RequiresLogin: method != "PostLogin" && !loginOptionalMethods[method],
+ LoginOptional: loginOptionalMethods[method],
RequiresAdminRole: strings.Contains(method, "Admin"),
}
}
@@ -144,6 +154,9 @@ func NewHandler(queries db.Querier, txm db.TxManager, hub GameHubInterface, auth
}
{{ end -}}
return h.impl.{{ .Name }}(ctx, request, user)
+ {{ else if .LoginOptional -}}
+ user, _ := GetUserFromContext(ctx)
+ return h.impl.{{ .Name }}(ctx, request, user)
{{ else -}}
return h.impl.{{ .Name }}(ctx, request)
{{ end -}}
diff --git a/frontend/app/App.tsx b/frontend/app/App.tsx
index 3f1333a..31adc28 100644
--- a/frontend/app/App.tsx
+++ b/frontend/app/App.tsx
@@ -26,9 +26,7 @@ export default function App() {
</PublicOnlyRoute>
</Route>
<Route path="/dashboard">
- <ProtectedRoute>
- <DashboardPage />
- </ProtectedRoute>
+ <DashboardPage />
</Route>
<Route path="/golf/:gameId/preview">
{(params) => (
@@ -52,18 +50,10 @@ export default function App() {
)}
</Route>
<Route path="/golf/:gameId/watch">
- {(params) => (
- <ProtectedRoute>
- <GolfWatchPage gameId={params.gameId} />
- </ProtectedRoute>
- )}
+ {(params) => <GolfWatchPage gameId={params.gameId} />}
</Route>
<Route path="/tournament/:tournamentId">
- {(params) => (
- <ProtectedRoute>
- <TournamentPage tournamentId={params.tournamentId} />
- </ProtectedRoute>
- )}
+ {(params) => <TournamentPage tournamentId={params.tournamentId} />}
</Route>
<Route>
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
diff --git a/frontend/app/pages/DashboardPage.tsx b/frontend/app/pages/DashboardPage.tsx
index 00db3f0..92c0f04 100644
--- a/frontend/app/pages/DashboardPage.tsx
+++ b/frontend/app/pages/DashboardPage.tsx
@@ -14,7 +14,7 @@ type Game = components["schemas"]["Game"];
export default function DashboardPage() {
usePageTitle(`Dashboard | ${APP_NAME}`);
- const { user, logout } = useAuth();
+ const { user, isLoggedIn, isLoading: authLoading, logout } = useAuth();
const [, navigate] = useLocation();
const [games, setGames] = useState<Game[]>([]);
@@ -33,7 +33,7 @@ export default function DashboardPage() {
navigate("/");
}
- if (loading) {
+ if (loading || authLoading) {
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<p className="text-gray-500">Loading...</p>
@@ -43,18 +43,24 @@ export default function DashboardPage() {
return (
<div className="p-6 bg-gray-100 min-h-screen flex flex-col items-center gap-4">
- {user?.icon_path && (
+ {isLoggedIn && user?.icon_path && (
<UserIcon
iconPath={user.icon_path}
displayName={user.display_name}
className="w-24 h-24"
/>
)}
- <h1 className="text-3xl font-bold text-gray-800">{user?.display_name}</h1>
+ {isLoggedIn ? (
+ <h1 className="text-3xl font-bold text-gray-800">
+ {user?.display_name}
+ </h1>
+ ) : (
+ <h1 className="text-3xl font-bold text-gray-800">試合一覧</h1>
+ )}
<BorderedContainerWithCaption caption="試合一覧">
<div className="px-4">
{games.length === 0 ? (
- <p>エントリーできる試合はありません</p>
+ <p>試合はありません</p>
) : (
<ul className="divide-y divide-gray-300">
{games.map((game) => (
@@ -68,20 +74,24 @@ export default function DashboardPage() {
</span>
</div>
<div className="flex gap-2">
- {game.started_at == null && (
+ {isLoggedIn && game.started_at == null && (
<NavigateLink to={`/golf/${game.game_id}/preview`}>
問題を見る
</NavigateLink>
)}
- <NavigateLink to={`/golf/${game.game_id}/play`}>
- 対戦
- </NavigateLink>
+ {isLoggedIn && (
+ <NavigateLink to={`/golf/${game.game_id}/play`}>
+ 対戦
+ </NavigateLink>
+ )}
<NavigateLink to={`/golf/${game.game_id}/watch`}>
観戦
</NavigateLink>
- <NavigateLink to={`/golf/${game.game_id}/submissions`}>
- 提出履歴
- </NavigateLink>
+ {isLoggedIn && (
+ <NavigateLink to={`/golf/${game.game_id}/submissions`}>
+ 提出履歴
+ </NavigateLink>
+ )}
</div>
</li>
))}
@@ -89,14 +99,18 @@ export default function DashboardPage() {
)}
</div>
</BorderedContainerWithCaption>
- <button
- type="button"
- onClick={handleLogout}
- className="px-4 py-2 bg-red-500 text-white rounded-sm transition duration-300 hover:bg-red-700 focus:ring-3 focus:ring-red-400 focus:outline-hidden"
- >
- ログアウト
- </button>
- {user?.is_admin && (
+ {isLoggedIn ? (
+ <button
+ type="button"
+ onClick={handleLogout}
+ className="px-4 py-2 bg-red-500 text-white rounded-sm transition duration-300 hover:bg-red-700 focus:ring-3 focus:ring-red-400 focus:outline-hidden"
+ >
+ ログアウト
+ </button>
+ ) : (
+ <NavigateLink to="/login">ログイン</NavigateLink>
+ )}
+ {isLoggedIn && user?.is_admin && (
<a
href={
import.meta.env.DEV
diff --git a/frontend/app/pages/GolfWatchPage.tsx b/frontend/app/pages/GolfWatchPage.tsx
index 4f76136..168bd6f 100644
--- a/frontend/app/pages/GolfWatchPage.tsx
+++ b/frontend/app/pages/GolfWatchPage.tsx
@@ -1,6 +1,5 @@
import { createStore, Provider as JotaiProvider } from "jotai";
import { useEffect, useMemo, useState } from "react";
-import { useLocation } from "wouter";
import { ApiClientContext, createApiClient } from "../api/client";
import type { components } from "../api/schema";
import GolfWatchApp from "../components/GolfWatchApp";
@@ -12,14 +11,13 @@ type LatestGameState = components["schemas"]["LatestGameState"];
type RankingEntry = components["schemas"]["RankingEntry"];
export default function GolfWatchPage({ gameId }: { gameId: string }) {
- const [, navigate] = useLocation();
-
const [game, setGame] = useState<Game | null>(null);
const [ranking, setRanking] = useState<RankingEntry[]>([]);
const [gameStates, setGameStates] = useState<{
[key: string]: LatestGameState;
}>({});
const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(false);
const gameIdNum = Number(gameId);
@@ -41,16 +39,16 @@ export default function GolfWatchPage({ gameId }: { gameId: string }) {
setRanking(ranking);
setGameStates(states);
})
- .catch(() => navigate("/dashboard"))
+ .catch(() => setError(true))
.finally(() => setLoading(false));
- }, [gameIdNum, navigate]);
+ }, [gameIdNum]);
const store = useMemo(() => {
if (!game) return null;
return createStore();
}, [game]);
- if (loading || !game || !store) {
+ if (loading) {
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<p className="text-gray-500">Loading...</p>
@@ -58,6 +56,14 @@ export default function GolfWatchPage({ gameId }: { gameId: string }) {
);
}
+ if (error || !game || !store) {
+ return (
+ <div className="min-h-screen bg-gray-100 flex items-center justify-center">
+ <p className="text-red-500">試合が見つかりませんでした</p>
+ </div>
+ );
+ }
+
return (
<JotaiProvider store={store}>
<ApiClientContext.Provider value={createApiClient()}>
diff --git a/frontend/app/pages/IndexPage.tsx b/frontend/app/pages/IndexPage.tsx
index 120720a..03e135b 100644
--- a/frontend/app/pages/IndexPage.tsx
+++ b/frontend/app/pages/IndexPage.tsx
@@ -31,7 +31,8 @@ export default function IndexPage() {
</p>
</BorderedContainer>
</div>
- <div>
+ <div className="flex gap-4">
+ <NavigateLink to="/dashboard">観戦する</NavigateLink>
<NavigateLink to="/login">ログイン</NavigateLink>
</div>
</div>