diff --git a/.env.sample b/.env.sample index 718f1e4..2051511 100644 --- a/.env.sample +++ b/.env.sample @@ -1,7 +1,8 @@ # Configure your dev environment -DATABASE_PORT= -DATABASE_USER= -DATABASE_PASSWORD= +DATABASE_PORT= +DATABASE_USER= +DATABASE_PASSWORD= + DATABASE_NAME=connect_four_reboot_admin PGRST_DB_ANON_ROLE=web_anon -PGRST_DB_SCHEMA=api \ No newline at end of file +PGRST_DB_SCHEMA=public \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..103b1e9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + + - name: Install dependencies + run: npm install + + - name: Check TypeScript types + run: npm run type-check + + # - name: Run tests + # run: npm test diff --git a/.gitignore b/.gitignore index 4427f8c..942c69f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build coverage/ .vscode .env +.vite/ # React Router /.react-router/ diff --git a/README.md b/README.md index 67f2c13..4851dac 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,14 @@ database with: make create-db ``` +tips: + +```sh +make run +``` + +this goal do all for you. + ## Installation connect-four-reboot-admin frontend Install the react-admin application dependencies by running: diff --git a/docker-compose.yml b/docker-compose.yml index 75e8203..e17f0b4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,14 +6,16 @@ services: depends_on: - database-postgres environment: - - PGRST_DB_URI=postgres://${DATABASE_USER}:${DATABASE_PASSWORD}@postgres:${DATABASE_PORT}/${DATABASE_NAME} + - PGRST_DB_URI=postgres://${DATABASE_USER}:${DATABASE_PASSWORD}@database-postgres:${DATABASE_PORT}/${DATABASE_NAME} - PGRST_DB_SCHEMA=${PGRST_DB_SCHEMA} - PGRST_DB_ANON_ROLE=${PGRST_DB_ANON_ROLE} + networks: + - your_network database-postgres: image: postgres:14 ports: - - "${DATABASE_PORT}:${DATABASE_PORT}" + - "${DATABASE_PORT}:5432" environment: - POSTGRES_USER=${DATABASE_USER} - POSTGRES_PASSWORD=${DATABASE_PASSWORD} @@ -21,6 +23,13 @@ services: volumes: - pgdata:/var/lib/postgresql/data - ./scripts:/scripts:ro + networks: + - your_network + healthcheck: + test: ["CMD", "pg_isready", "-U", "${DATABASE_USER}"] + interval: 10s + timeout: 5s + retries: 5 networks: your_network: diff --git a/makefile b/makefile index c24f539..3ee5a44 100644 --- a/makefile +++ b/makefile @@ -20,6 +20,15 @@ build : ## build the react-admin server. run-ra-dev: ## run the react-admin server. npm run dev +## Run +######## + +run: ## fresh run of all you need to use the app + make run-postgrest-docker && make create-model && make populate-db && make run-ra-dev + +stop: ## stop the docker + make stop-postgrest-docker + ## Docker - postrgrest / postgres ################################# @@ -39,7 +48,8 @@ create-db: ## initialize an empty ready to use db inside the docker - use it onl docker exec -i connect-four-reboot-admin-database-postgres-1 sh -c 'psql -U $(DATABASE_USER) -c "CREATE DATABASE $(DATABASE_NAME);"' drop-db: ## drop the postgres db inside the docker. - docker exec -i connect-four-reboot-admin-database-postgres-1 sh -c 'psql -U $(DATABASE_USER) -c "DROP DATABASE IF EXISTS $(DATABASE_NAME);"' + docker exec -i connect-four-reboot-admin-database-postgres-1 sh -c \ +'psql -U postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '\''connect_four_reboot_admin'\'';"' && docker exec -i connect-four-reboot-admin-database-postgres-1 sh -c 'psql -U $(DATABASE_USER) -c "DROP DATABASE IF EXISTS $(DATABASE_NAME);"' create-model: ## create the connect-four-reboot-admin tables. docker exec connect-four-reboot-admin-database-postgres-1 sh -c 'psql -U $(DATABASE_USER) -d $(DATABASE_NAME) -f /scripts/create_model.sql' @@ -47,6 +57,10 @@ create-model: ## create the connect-four-reboot-admin tables. populate-db: ## populate database with fake values npx tsx tools/populateDbWithFakeData.ts +reset-db: + make drop-db && make create-db && make create-model && make populate-db + + ## Dev quality ############## @@ -58,3 +72,4 @@ lint: ## run linter format: ## run prettier npm run format + diff --git a/package-lock.json b/package-lock.json index c90d968..429b02b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@emotion/styled": "^11.14.0", "@mui/icons-material": "^6.4.0", "@mui/material": "^6.4.0", + "@raphiniert/ra-data-postgrest": "^2.4.1", + "@types/pg": "^8.11.11", "dotenv": "^16.4.7", "faker": "^6.6.6", "pg": "^8.13.1", @@ -1352,6 +1354,19 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@raphiniert/ra-data-postgrest": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@raphiniert/ra-data-postgrest/-/ra-data-postgrest-2.4.1.tgz", + "integrity": "sha512-2v4XE/9wy7dTfafJuDFDb3u0ZvyBpURZ0hNv3uc0TcnxLR+sXHYeOv3WNzIMKLKN2dsDllU3qytQE63/Z8SmLA==", + "license": "MIT", + "dependencies": { + "lodash.isequal": "^4.5.0", + "qs": "^6.12.1" + }, + "peerDependencies": { + "ra-core": "^3.0.0 || ^4.1.0 || ^5.0.1" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.32.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz", @@ -2441,7 +2456,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3340,6 +3354,7 @@ "version": "6.6.6", "resolved": "https://registry.npmjs.org/faker/-/faker-6.6.6.tgz", "integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==", + "dev": true, "license": "MIT" }, "node_modules/fast-deep-equal": { @@ -4497,6 +4512,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4645,7 +4667,6 @@ "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4900,6 +4921,7 @@ "version": "8.13.1", "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz", "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==", + "dev": true, "license": "MIT", "dependencies": { "pg-connection-string": "^2.7.0", @@ -4927,6 +4949,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "dev": true, "license": "MIT", "optional": true }, @@ -4934,12 +4957,14 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==", + "dev": true, "license": "MIT" }, "node_modules/pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "dev": true, "license": "ISC", "engines": { "node": ">=4.0.0" @@ -4959,6 +4984,7 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz", "integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==", + "dev": true, "license": "MIT", "peerDependencies": { "pg": ">=8.0" @@ -4968,12 +4994,14 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz", "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==", + "dev": true, "license": "MIT" }, "node_modules/pg-types": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dev": true, "license": "MIT", "dependencies": { "pg-int8": "1.0.1", @@ -4990,6 +5018,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dev": true, "license": "MIT", "dependencies": { "split2": "^4.1.0" @@ -5057,6 +5086,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -5066,6 +5096,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5075,6 +5106,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5084,6 +5116,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dev": true, "license": "MIT", "dependencies": { "xtend": "^4.0.0" @@ -5152,6 +5185,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -5759,7 +5807,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5779,7 +5826,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5796,7 +5842,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5815,7 +5860,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5873,6 +5917,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, "license": "ISC", "engines": { "node": ">= 10.x" @@ -6476,6 +6521,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4" diff --git a/package.json b/package.json index db1cdea..102cb8a 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "@emotion/styled": "^11.14.0", "@mui/icons-material": "^6.4.0", "@mui/material": "^6.4.0", + "@raphiniert/ra-data-postgrest": "^2.4.1", + "@types/pg": "^8.11.11", "dotenv": "^16.4.7", "faker": "^6.6.6", "react": "^19.0.0", diff --git a/scripts/create_model.sql b/scripts/create_model.sql index 4121480..d51b914 100644 --- a/scripts/create_model.sql +++ b/scripts/create_model.sql @@ -2,7 +2,7 @@ CREATE ROLE web_anon NOLOGIN; GRANT USAGE ON SCHEMA public TO web_anon; ALTER DEFAULT PRIVILEGES IN SCHEMA public -GRANT SELECT, INSERT, UPDATE, DELETE ON tables TO web_anon; +GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO web_anon; CREATE TABLE IF NOT EXISTS leagues ( id SERIAL PRIMARY KEY, diff --git a/src/App.tsx b/src/App.tsx index 8da1820..8f6243a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,24 @@ -import { - Admin, - Resource, - ListGuesser, - EditGuesser, - ShowGuesser, -} from "react-admin"; -import { Layout } from "./Layout"; - -export const App = () => ; +import { Admin, Resource, fetchUtils } from "react-admin"; + +import postgrestRestProvider, { + IDataProviderConfig, + defaultPrimaryKeys, + defaultSchema, +} from "@raphiniert/ra-data-postgrest"; +import { GameList } from "./games/GameList"; + +const config: IDataProviderConfig = { + apiUrl: "http://localhost:3000", + httpClient: fetchUtils.fetchJson, + defaultListOp: "eq", + primaryKeys: defaultPrimaryKeys, + schema: defaultSchema, +}; + +const App = () => ( + + + +); + +export default App; diff --git a/src/games/GameList.tsx b/src/games/GameList.tsx new file mode 100644 index 0000000..c24e2b2 --- /dev/null +++ b/src/games/GameList.tsx @@ -0,0 +1,27 @@ +import { + Datagrid, + DateField, + List, + ReferenceField, + TextField, + WrapperField, +} from "react-admin"; +import { GameStatus } from "./GameStatus"; + +export const GameList = () => ( + + + + + + + + + + + + + + + +); diff --git a/src/games/GameStatus.tsx b/src/games/GameStatus.tsx new file mode 100644 index 0000000..03a2197 --- /dev/null +++ b/src/games/GameStatus.tsx @@ -0,0 +1,35 @@ +import { Box, Tooltip, Typography } from "@mui/material"; +import { useFieldValue } from "react-admin"; + +const Status = { + Ongoing: { label: "Ongoing", color: "#4CAF50" }, + Finished: { label: "Finished", color: "#9E9E9E" }, +} as const; + +export const GameStatus = () => { + const gameState = useFieldValue({ source: "game_state" }); + + const gameStateVal = JSON.parse(gameState); + const status = + gameStateVal.victoryState.player != null || + gameStateVal.victoryState?.isDraw + ? Status.Finished + : Status.Ongoing; + + return ( + <> + + + + {status.label} + + + + ); +}; diff --git a/src/index.tsx b/src/index.tsx index d985c30..3d57d26 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { RouterProvider } from "react-router"; import { createHashRouter } from "react-router-dom"; -import { App } from "./App"; +import App from "./App"; const router = createHashRouter([ {