Skip to content

Commit 3f7a5f8

Browse files
authored
Redis に移行 (#107)
* fix: proceed to jikyoku * redis setup * dark mode and go back to root from learn * switch to redis
1 parent 350b079 commit 3f7a5f8

File tree

9 files changed

+249
-69
lines changed

9 files changed

+249
-69
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
NODE_ENV=development
22
DATABASE_URL=postgres://user:postgres@localhost:5432/
3-
BETTER_AUTH_SECRET=
3+
BETTER_AUTH_SECRET=dev-dev-dev-change-me
44
BETTER_AUTH_URL=http://localhost:5173
5+
REDIS_URL=redis://:devpass@localhost:6379

GEMINI.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Repository Structure Summary
2+
3+
This document provides an overview of the directory structure and key files in the `hitori-mahjong` repository.
4+
5+
## Overview
6+
7+
This project is a web application for playing a solo (hitori) version of Mahjong. It appears to be built with a modern web stack, utilizing server-side rendering with Cloudflare Workers.
8+
9+
## Technology Stack
10+
11+
- **Frontend:** React Router v7
12+
- **Backend:** React Router v7 + Cloudflare Workers
13+
- **Database:** Drizzle ORM (likely with a service like Cloudflare D1)
14+
- **Authentication:** Better Auth (see `app/lib/auth.ts`)
15+
- **Styling:** Tailwind CSS + daisyUI (`app/app.css`)
16+
- **Package Manager:** Bun
17+
18+
## Top-Level Files
19+
20+
- `package.json`: Defines project scripts, dependencies, and metadata.
21+
- `vite.config.ts`: Configuration for the Vite frontend build tool.
22+
- `wrangler.jsonc`: Configuration for Cloudflare Workers deployment.
23+
- `drizzle.config.ts`: Configuration for the Drizzle ORM.
24+
- `tsconfig.json`: Base TypeScript configuration.
25+
- `biome.jsonc`: Configuration for the Biome linter/formatter.
26+
- `lefthook.yml`: Git hooks configuration.
27+
- `docker-compose.yml`: Defines services for local development, likely for a database or Redis.
28+
29+
## Core Directories
30+
31+
### `app/`
32+
33+
This is the main source code for the frontend application.
34+
35+
- `app/root.tsx`: The root component of the React application.
36+
- `app/entry.server.tsx`: The server-side rendering entry point.
37+
- `app/routes/`: Contains the route components for the application, following a file-based routing convention.
38+
- `_index.tsx`: The home page.
39+
- `learn.tsx`: A tutorial or learning page.
40+
- `api.auth.$.ts`: API routes for handling authentication.
41+
- `app/lib/`: Contains core application logic, utilities, and components.
42+
- `auth.ts` & `auth-client.ts`: Authentication logic for server and client.
43+
- `hai.ts`: Core logic related to Mahjong tiles (牌).
44+
- `redis.ts`: Redis client setup.
45+
- `db/`: Database schema (`schema.ts`) and client initialization (`index.ts`).
46+
- `components/`: Reusable React components.
47+
48+
### `public/`
49+
50+
Contains static assets that are served directly to the client.
51+
52+
- `hai/`: Images for all the Mahjong tiles.
53+
- `tutorial/`: Images used in the tutorial sections.
54+
- `background/`: Background images for the application.
55+
- Other assets like `favicon.ico` and `logo.svg`.
56+
57+
### `workers/`
58+
59+
This directory likely contains the source code for the Cloudflare Worker that serves as the backend.
60+
61+
- `app.ts`: The main application logic for the worker.
62+
63+
### `old-workspaces/`
64+
65+
This directory appears to contain a previous version of the project, possibly from a monorepo structure. It is likely not in active use but is kept for reference. It is divided into `server`, `shared`, and `web` packages.
66+
67+
### `.github/`
68+
69+
Contains GitHub Actions workflows for CI/CD.
70+
71+
- `check.yml`: A workflow that likely runs on pull requests to perform checks like linting, testing, and building.
72+
- `scheduled-request.yml`: A workflow that runs on a schedule.

app/lib/redis.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { createClient } from "redis";
2+
import type { Hai } from "./hai";
3+
import { sortTehai } from "./hai";
4+
5+
export function getRedisClient(env: Env) {
6+
const client = createClient({
7+
url: env.REDIS_URL,
8+
});
9+
return client;
10+
}
11+
12+
interface GameState {
13+
kyoku: number;
14+
junme: number;
15+
haiyama: Hai[];
16+
sutehai: Hai[];
17+
tehai: Hai[];
18+
tsumohai: Hai | null;
19+
}
20+
21+
const getGameState = async (
22+
client: ReturnType<typeof createClient>,
23+
userId: string,
24+
): Promise<GameState | null> => {
25+
const gameStateJSON = await client.get(`user:${userId}:game`);
26+
if (!gameStateJSON) {
27+
return null;
28+
}
29+
return JSON.parse(gameStateJSON);
30+
};
31+
32+
const setGameState = async (
33+
client: ReturnType<typeof createClient>,
34+
userId: string,
35+
gameState: GameState,
36+
) => {
37+
await client.set(`user:${userId}:game`, JSON.stringify(gameState));
38+
};
39+
40+
export const init = async (
41+
client: ReturnType<typeof createClient>,
42+
userId: string,
43+
initHaiyama: Hai[],
44+
) => {
45+
const tehai = initHaiyama.slice(0, 13);
46+
const tsumohai = initHaiyama[13];
47+
const haiyama = initHaiyama.slice(14);
48+
const initialGameState: GameState = {
49+
kyoku: 1,
50+
junme: 1,
51+
haiyama,
52+
sutehai: [],
53+
tehai,
54+
tsumohai,
55+
};
56+
await setGameState(client, userId, initialGameState);
57+
};
58+
59+
export const tedashi = async (
60+
client: ReturnType<typeof createClient>,
61+
userId: string,
62+
index: number,
63+
) => {
64+
const state = await getGameState(client, userId);
65+
if (!state) {
66+
throw new Error("game not found");
67+
}
68+
if (!state.tsumohai) {
69+
throw new Error("syohai");
70+
}
71+
const tsumohai = state.tsumohai;
72+
73+
if (index < 0 || 12 < index) {
74+
throw new Error("index out of tehai length");
75+
}
76+
const deletedTehai = state.tehai.filter((_, i) => i !== index);
77+
const discardedHai = state.tehai[index];
78+
79+
const newGameState: GameState = {
80+
...state,
81+
junme: state.junme + 1,
82+
haiyama: state.haiyama.slice(1),
83+
sutehai: [...state.sutehai, discardedHai],
84+
tehai: sortTehai([...deletedTehai, tsumohai]),
85+
tsumohai: state.haiyama[0],
86+
};
87+
await setGameState(client, userId, newGameState);
88+
};
89+
90+
export const tsumogiri = async (
91+
client: ReturnType<typeof createClient>,
92+
userId: string,
93+
) => {
94+
const state = await getGameState(client, userId);
95+
if (!state) {
96+
throw new Error("game not found");
97+
}
98+
if (!state.tsumohai) {
99+
throw new Error("syohai");
100+
}
101+
const tsumohai = state.tsumohai;
102+
const newGameState: GameState = {
103+
...state,
104+
junme: state.junme + 1,
105+
haiyama: state.haiyama.slice(1),
106+
sutehai: [...state.sutehai, tsumohai],
107+
tsumohai: state.haiyama[0],
108+
};
109+
await setGameState(client, userId, newGameState);
110+
};
111+
112+
export const jikyoku = async (
113+
client: ReturnType<typeof createClient>,
114+
userId: string,
115+
) => {
116+
const state = await getGameState(client, userId);
117+
if (!state) {
118+
throw new Error("game not found");
119+
}
120+
const newGameState: GameState = {
121+
...state,
122+
kyoku: state.kyoku + 1,
123+
};
124+
await setGameState(client, userId, newGameState);
125+
};

app/lib/store.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

app/routes/_index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { drizzle as drizzleNeon } from "drizzle-orm/neon-http";
22
import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
33
import { Link } from "react-router";
4+
import { getRedisClient } from "~/lib/redis";
45
import type { Route } from "./+types/_index";
56

67
export async function loader({ context }: Route.LoaderArgs) {
@@ -9,6 +10,12 @@ export async function loader({ context }: Route.LoaderArgs) {
910
env.NODE_ENV === "development"
1011
? drizzlePg(env.DATABASE_URL)
1112
: drizzleNeon(env.DATABASE_URL);
13+
const redisClient = getRedisClient(env);
14+
redisClient.on("error", (err) => console.log("Redis Client Error", err));
15+
await redisClient.connect();
16+
await redisClient.set("key", "value");
17+
const value = await redisClient.get("key");
18+
console.log(value);
1219
}
1320

1421
export default function Page() {

app/routes/learn.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export default function Page() {
1010
<div className="drawer drawer-open">
1111
<input id="my-drawer-4" type="checkbox" className="drawer-toggle" />
1212
<div className="drawer-content ml-4 mt-4">
13+
<Link to="/" className="btn btn-outline mb-4">
14+
Back to Home
15+
</Link>
1316
<div id="basic">
1417
<BasicRule />
1518
</div>
@@ -24,7 +27,7 @@ export default function Page() {
2427
aria-label="close sidebar"
2528
className="drawer-overlay"
2629
></label>
27-
<div className="is-drawer-close:w-14 is-drawer-open:w-48 bg-gray-100 flex flex-col items-start min-h-full">
30+
<div className="is-drawer-close:w-14 is-drawer-open:w-48 bg-base-200 flex flex-col items-start min-h-full">
2831
<ul className="menu w-full grow">
2932
{contents.map((content) => {
3033
if (currentHash === `#${content}`) {

bun.lock

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"react": "^19.1.1",
1414
"react-dom": "^19.1.1",
1515
"react-router": "^7.9.2",
16+
"redis": "^5.9.0",
1617
"zustand": "^5.0.8",
1718
},
1819
"devDependencies": {
@@ -23,6 +24,7 @@
2324
"@types/pg": "^8.15.6",
2425
"@types/react": "^19.1.13",
2526
"@types/react-dom": "^19.1.9",
27+
"@types/redis": "^4.0.11",
2628
"daisyui": "^5.3.10",
2729
"drizzle-kit": "^0.31.5",
2830
"lefthook": "^2.0.2",
@@ -283,6 +285,16 @@
283285

284286
"@react-router/node": ["@react-router/[email protected]", "", { "dependencies": { "@mjackson/node-fetch-server": "^0.2.0" }, "peerDependencies": { "react-router": "7.9.4", "typescript": "^5.1.0" }, "optionalPeers": ["typescript"] }, "sha512-sdeDNRaqAB71BR2hPlhcQbPbrXh8uGJUjLVc+NpRiPsQbv6B8UvIucN4IX9YGVJkw3UxVQBn2vPSwxACAck32Q=="],
285287

288+
"@redis/bloom": ["@redis/[email protected]", "", { "peerDependencies": { "@redis/client": "^5.9.0" } }, "sha512-W9D8yfKTWl4tP8lkC3MRYkMz4OfbuzE/W8iObe0jFgoRmgMfkBV+Vj38gvIqZPImtY0WB34YZkX3amYuQebvRQ=="],
289+
290+
"@redis/client": ["@redis/[email protected]", "", { "dependencies": { "cluster-key-slot": "1.1.2" } }, "sha512-EI0Ti5pojD2p7TmcS7RRa+AJVahdQvP/urpcSbK/K9Rlk6+dwMJTQ354pCNGCwfke8x4yKr5+iH85wcERSkwLQ=="],
291+
292+
"@redis/json": ["@redis/[email protected]", "", { "peerDependencies": { "@redis/client": "^5.9.0" } }, "sha512-Bm2jjLYaXdUWPb9RaEywxnjmzw7dWKDZI4MS79mTWPV16R982jVWBj6lY2ZGelJbwxHtEVg4/FSVgYDkuO/MxA=="],
293+
294+
"@redis/search": ["@redis/[email protected]", "", { "peerDependencies": { "@redis/client": "^5.9.0" } }, "sha512-jdk2csmJ29DlpvCIb2ySjix2co14/0iwIT3C0I+7ZaToXgPbgBMB+zfEilSuncI2F9JcVxHki0YtLA0xX3VdpA=="],
295+
296+
"@redis/time-series": ["@redis/[email protected]", "", { "peerDependencies": { "@redis/client": "^5.9.0" } }, "sha512-W6ILxcyOqhnI7ELKjJXOktIg3w4+aBHugDbVpgVLPZ+YDjObis1M0v7ZzwlpXhlpwsfePfipeSK+KWNuymk52w=="],
297+
286298
"@remix-run/node-fetch-server": ["@remix-run/[email protected]", "", {}, "sha512-J1dev372wtJqmqn9U/qbpbZxbJSQrogNN2+Qv1lKlpATpe/WQ9aCZfl/xSb9d2Rgh1IyLSvNxZAXPZxruO6Xig=="],
287299

288300
"@rollup/rollup-android-arm-eabi": ["@rollup/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="],
@@ -377,6 +389,8 @@
377389

378390
"@types/react-dom": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
379391

392+
"@types/redis": ["@types/[email protected]", "", { "dependencies": { "redis": "*" } }, "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg=="],
393+
380394
"acorn": ["[email protected]", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
381395

382396
"acorn-walk": ["[email protected]", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
@@ -413,6 +427,8 @@
413427

414428
"chokidar": ["[email protected]", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
415429

430+
"cluster-key-slot": ["[email protected]", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
431+
416432
"color": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
417433

418434
"color-convert": ["[email protected]", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@@ -663,6 +679,8 @@
663679

664680
"readdirp": ["[email protected]", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
665681

682+
"redis": ["[email protected]", "", { "dependencies": { "@redis/bloom": "5.9.0", "@redis/client": "5.9.0", "@redis/json": "5.9.0", "@redis/search": "5.9.0", "@redis/time-series": "5.9.0" } }, "sha512-E8dQVLSyH6UE/C9darFuwq4usOPrqfZ1864kI4RFbr5Oj9ioB9qPF0oJMwX7s8mf6sPYrz84x/Dx1PGF3/0EaQ=="],
683+
666684
"reflect-metadata": ["[email protected]", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
667685

668686
"resolve-pkg-maps": ["[email protected]", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],

docker-compose.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,26 @@ services:
1212
networks:
1313
- mynetwork
1414

15+
redis:
16+
image: redis:7-alpine
17+
container_name: myredis
18+
command: ["redis-server", "--appendonly", "yes", "--requirepass", "devpass"]
19+
ports:
20+
- 6379:6379
21+
volumes:
22+
- redis-data:/data
23+
healthcheck:
24+
test: ["CMD", "redis-cli", "-a", "devpass", "PING"]
25+
interval: 5s
26+
timeout: 3s
27+
retries: 10
28+
networks:
29+
- mynetwork
30+
1531
networks:
1632
mynetwork:
1733
driver: bridge
1834

1935
volumes:
2036
db-store:
37+
redis-data:

0 commit comments

Comments
 (0)