mirror of
https://github.com/MetaFam/TheGame.git
synced 2026-01-10 07:07:55 -05:00
Base architecture (#1)
* Hasura and base models * Added role public SELECT permissions * Added role player UPDATE permissions * basic backend api * Added SELECT permissions for player * Update backend to typescript * init app-react * Add apollo * graphql-codegen not working well... * Added web3 to web app * connecting frontend with web3 * Auth webhook verifies eth signature * Update frontend to fetch player_id
This commit is contained in:
6
.editorconfig
Normal file
6
.editorconfig
Normal file
@@ -0,0 +1,6 @@
|
||||
root = true
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
9
.env.sample
Normal file
9
.env.sample
Normal file
@@ -0,0 +1,9 @@
|
||||
COMPOSE_PROJECT_NAME=the-game
|
||||
|
||||
DATABASE_USER=metagame
|
||||
DATABASE_PASSWORD=metagame_secret
|
||||
DATABASE_NAME=thegame
|
||||
|
||||
HASURA_GRAPHQL_ADMIN_SECRET=metagame_secret
|
||||
|
||||
HASURA_PORT=8080
|
||||
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
node_modules
|
||||
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
.pnp.*
|
||||
|
||||
.pnp.js
|
||||
yarn-error.log
|
||||
|
||||
.env
|
||||
33
README.md
33
README.md
@@ -1,3 +1,36 @@
|
||||
# The Game
|
||||
|
||||
Monorepo for the MetaGame applications, backend and databases.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
### Bootstrap
|
||||
|
||||
```shell script
|
||||
cp .env.sample .env
|
||||
npm run docker:start
|
||||
```
|
||||
|
||||
### Tooling
|
||||
|
||||
Start Hasura console
|
||||
|
||||
```shell script
|
||||
npm run hasura:console
|
||||
```
|
||||
|
||||
Hasura CLI example
|
||||
|
||||
```shell script
|
||||
npm run hasura -- migrate squash 1586952135212
|
||||
```
|
||||
|
||||
[Hasura CLI documentation](https://hasura.io/docs/1.0/graphql/manual/hasura-cli/index.html)
|
||||
|
||||
### Restart with fresh database
|
||||
|
||||
```shell script
|
||||
npm run docker:clean
|
||||
npm run docker:start:local
|
||||
```
|
||||
|
||||
35
docker-compose.yml
Normal file
35
docker-compose.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
version: '3.6'
|
||||
|
||||
services:
|
||||
hasura:
|
||||
build: ./hasura
|
||||
depends_on:
|
||||
- database
|
||||
ports:
|
||||
- ${HASURA_PORT}:8080
|
||||
environment:
|
||||
PORT: 8080
|
||||
DATABASE_URL: postgres://${DATABASE_USER}:${DATABASE_PASSWORD}@database:5432/${DATABASE_NAME}
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_GRAPHQL_ADMIN_SECRET}
|
||||
HASURA_GRAPHQL_AUTH_HOOK: http://backend:4000/auth-webhook
|
||||
|
||||
database:
|
||||
image: postgres:12
|
||||
volumes:
|
||||
- database:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: ${DATABASE_USER}
|
||||
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
||||
POSTGRES_DB: ${DATABASE_NAME}
|
||||
|
||||
backend:
|
||||
build: ./packages/backend
|
||||
environment:
|
||||
PORT: 4000
|
||||
GRAPHQL_URL: http://hasura:8080/v1/graphql
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_GRAPHQL_ADMIN_SECRET}
|
||||
|
||||
volumes:
|
||||
database:
|
||||
...
|
||||
19
hasura/Dockerfile
Normal file
19
hasura/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM hasura/graphql-engine:v1.1.1.cli-migrations
|
||||
|
||||
## Default setup
|
||||
|
||||
ENV HASURA_GRAPHQL_ENABLE_TELEMETRY false
|
||||
ENV HASURA_GRAPHQL_ENABLE_CONSOLE false
|
||||
ENV HASURA_GRAPHQL_ENABLED_LOG_TYPES startup, http-log, webhook-log, websocket-log, query-log
|
||||
|
||||
## Migrations
|
||||
|
||||
COPY migrations /hasura-migrations
|
||||
ENV HASURA_GRAPHQL_MIGRATIONS_DATABASE_ENV_VAR DATABASE_URL
|
||||
|
||||
## Execution
|
||||
|
||||
CMD graphql-engine \
|
||||
--database-url $DATABASE_URL \
|
||||
serve \
|
||||
--server-port $PORT
|
||||
2
hasura/config.yaml
Normal file
2
hasura/config.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
endpoint: http://localhost:8080
|
||||
admin_secret: metagame_secret
|
||||
99
hasura/migrations/1575910000000_init/up.sql
Normal file
99
hasura/migrations/1575910000000_init/up.sql
Normal file
@@ -0,0 +1,99 @@
|
||||
-- Enums
|
||||
|
||||
CREATE TYPE "Profile_Type" AS ENUM (
|
||||
'ETHEREUM',
|
||||
'DISCORD',
|
||||
'GITHUB',
|
||||
'DISCOURSE'
|
||||
);
|
||||
|
||||
CREATE TYPE "Rank" AS ENUM (
|
||||
'PLAYER',
|
||||
'BRONZE',
|
||||
'SILVER',
|
||||
'GOLDEN',
|
||||
'PLATINIUM',
|
||||
'DIAMOND'
|
||||
);
|
||||
|
||||
-- Tables
|
||||
|
||||
CREATE TABLE "Player" (
|
||||
"id" uuid DEFAULT public.gen_random_uuid() NOT NULL,
|
||||
"totalXp" numeric DEFAULT 0,
|
||||
"rank" "Rank" NOT NULL DEFAULT 'PLAYER',
|
||||
"links" json,
|
||||
"sentences" json
|
||||
);
|
||||
|
||||
CREATE TABLE "Profile" (
|
||||
"player_id" uuid NOT NULL,
|
||||
"identifier" text NOT NULL,
|
||||
"linkToProof" text,
|
||||
"type" "Profile_Type" NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "Quest" (
|
||||
"id" uuid DEFAULT public.gen_random_uuid() NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"url" text NOT NULL,
|
||||
"xp" numeric NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE "Quest_Completed" (
|
||||
"quest_id" uuid NOT NULL,
|
||||
"player_id" uuid NOT NULL,
|
||||
"time" timestamp
|
||||
);
|
||||
|
||||
CREATE TABLE "XPInterval" (
|
||||
"player_id" uuid NOT NULL,
|
||||
"startTime" date,
|
||||
"endTime" date,
|
||||
"xp" numeric NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "Guild" (
|
||||
"id" uuid DEFAULT public.gen_random_uuid() NOT NULL,
|
||||
"name" text
|
||||
);
|
||||
|
||||
CREATE TABLE "Guild_Member" (
|
||||
"guild_id" uuid NOT NULL,
|
||||
"player_id" uuid NOT NULL
|
||||
);
|
||||
|
||||
-- Primary keys
|
||||
|
||||
ALTER TABLE ONLY public."Player"
|
||||
ADD CONSTRAINT "Player_pkey" PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY public."Quest"
|
||||
ADD CONSTRAINT "Quest_pkey" PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY public."Guild"
|
||||
ADD CONSTRAINT "Guild_pkey" PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY public."Quest_Completed"
|
||||
ADD CONSTRAINT "Quest_Completed_pkey" PRIMARY KEY (quest_id, player_id);
|
||||
|
||||
ALTER TABLE ONLY public."Guild_Member"
|
||||
ADD CONSTRAINT "Guild_Member_pkey" PRIMARY KEY (guild_id, player_id);
|
||||
|
||||
-- Uniques
|
||||
|
||||
ALTER TABLE ONLY public."Profile"
|
||||
ADD CONSTRAINT "Profile_identifier_key" UNIQUE (identifier);
|
||||
|
||||
-- Foreign keys
|
||||
|
||||
ALTER TABLE "Profile" ADD CONSTRAINT "Profile_player_id_fkey" FOREIGN KEY ("player_id") REFERENCES "Player" ("id");
|
||||
|
||||
ALTER TABLE "Quest_Completed" ADD CONSTRAINT "Quest_Completed_player_id_fkey" FOREIGN KEY ("player_id") REFERENCES "Player" ("id");
|
||||
ALTER TABLE "Quest_Completed" ADD CONSTRAINT "Quest_Completed_quest_id_fkey" FOREIGN KEY ("quest_id") REFERENCES "Quest" ("id");
|
||||
|
||||
ALTER TABLE "Guild_Member" ADD CONSTRAINT "Guild_Member_player_id_fkey" FOREIGN KEY ("player_id") REFERENCES "Player" ("id");
|
||||
ALTER TABLE "Guild_Member" ADD CONSTRAINT "Guild_Member_guild_id_fkey" FOREIGN KEY ("guild_id") REFERENCES "Guild" ("id");
|
||||
|
||||
ALTER TABLE "XPInterval" ADD CONSTRAINT "XPInterval_player_id_fkey" FOREIGN KEY ("player_id") REFERENCES "Player" ("id");
|
||||
92
hasura/migrations/1575910000000_init/up.yaml
Normal file
92
hasura/migrations/1575910000000_init/up.yaml
Normal file
@@ -0,0 +1,92 @@
|
||||
- args:
|
||||
tables:
|
||||
- array_relationships:
|
||||
- name: Guild_Members
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: guild_id
|
||||
table:
|
||||
name: Guild_Member
|
||||
schema: public
|
||||
table:
|
||||
name: Guild
|
||||
schema: public
|
||||
- object_relationships:
|
||||
- name: Guild
|
||||
using:
|
||||
foreign_key_constraint_on: guild_id
|
||||
- name: Player
|
||||
using:
|
||||
foreign_key_constraint_on: player_id
|
||||
table:
|
||||
name: Guild_Member
|
||||
schema: public
|
||||
- array_relationships:
|
||||
- name: Guild_Members
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: player_id
|
||||
table:
|
||||
name: Guild_Member
|
||||
schema: public
|
||||
- name: Profiles
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: player_id
|
||||
table:
|
||||
name: Profile
|
||||
schema: public
|
||||
- name: Quest_Completeds
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: player_id
|
||||
table:
|
||||
name: Quest_Completed
|
||||
schema: public
|
||||
- name: XPIntervals
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: player_id
|
||||
table:
|
||||
name: XPInterval
|
||||
schema: public
|
||||
table:
|
||||
name: Player
|
||||
schema: public
|
||||
- object_relationships:
|
||||
- name: Player
|
||||
using:
|
||||
foreign_key_constraint_on: player_id
|
||||
table:
|
||||
name: Profile
|
||||
schema: public
|
||||
- array_relationships:
|
||||
- name: Quest_Completeds
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: quest_id
|
||||
table:
|
||||
name: Quest_Completed
|
||||
schema: public
|
||||
table:
|
||||
name: Quest
|
||||
schema: public
|
||||
- object_relationships:
|
||||
- name: Player
|
||||
using:
|
||||
foreign_key_constraint_on: player_id
|
||||
- name: Quest
|
||||
using:
|
||||
foreign_key_constraint_on: quest_id
|
||||
table:
|
||||
name: Quest_Completed
|
||||
schema: public
|
||||
- object_relationships:
|
||||
- name: Player
|
||||
using:
|
||||
foreign_key_constraint_on: player_id
|
||||
table:
|
||||
name: XPInterval
|
||||
schema: public
|
||||
version: 2
|
||||
type: replace_metadata
|
||||
42
hasura/migrations/1586952306059_role_public/down.yaml
Normal file
42
hasura/migrations/1586952306059_role_public/down.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
- args:
|
||||
role: public
|
||||
table:
|
||||
name: XPInterval
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: public
|
||||
table:
|
||||
name: Quest_Completed
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: public
|
||||
table:
|
||||
name: Quest
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: public
|
||||
table:
|
||||
name: Profile
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: public
|
||||
table:
|
||||
name: Guild_Member
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: public
|
||||
table:
|
||||
name: Guild
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: public
|
||||
table:
|
||||
name: Player
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
109
hasura/migrations/1586952306059_role_public/up.yaml
Normal file
109
hasura/migrations/1586952306059_role_public/up.yaml
Normal file
@@ -0,0 +1,109 @@
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- id
|
||||
- totalXp
|
||||
- rank
|
||||
- links
|
||||
- sentences
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: public
|
||||
table:
|
||||
name: Player
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- id
|
||||
- name
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: public
|
||||
table:
|
||||
name: Guild
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- guild_id
|
||||
- player_id
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: public
|
||||
table:
|
||||
name: Guild_Member
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- player_id
|
||||
- identifier
|
||||
- linkToProof
|
||||
- type
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: public
|
||||
table:
|
||||
name: Profile
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- id
|
||||
- name
|
||||
- description
|
||||
- url
|
||||
- xp
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: public
|
||||
table:
|
||||
name: Quest
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- quest_id
|
||||
- player_id
|
||||
- time
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: public
|
||||
table:
|
||||
name: Quest_Completed
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- player_id
|
||||
- startTime
|
||||
- endTime
|
||||
- xp
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: public
|
||||
table:
|
||||
name: XPInterval
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
48
hasura/migrations/1586952972045_role_player/down.yaml
Normal file
48
hasura/migrations/1586952972045_role_player/down.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
- args:
|
||||
role: player
|
||||
table:
|
||||
name: XPInterval
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: player
|
||||
table:
|
||||
name: Quest_Completed
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: player
|
||||
table:
|
||||
name: Quest
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: player
|
||||
table:
|
||||
name: Profile
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: player
|
||||
table:
|
||||
name: Guild_Member
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: player
|
||||
table:
|
||||
name: Guild
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: player
|
||||
table:
|
||||
name: Player
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
role: player
|
||||
table:
|
||||
name: Player
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
125
hasura/migrations/1586952972045_role_player/up.yaml
Normal file
125
hasura/migrations/1586952972045_role_player/up.yaml
Normal file
@@ -0,0 +1,125 @@
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- id
|
||||
- totalXp
|
||||
- rank
|
||||
- links
|
||||
- sentences
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: player
|
||||
table:
|
||||
name: Player
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- id
|
||||
- name
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: player
|
||||
table:
|
||||
name: Guild
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- guild_id
|
||||
- player_id
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: player
|
||||
table:
|
||||
name: Guild_Member
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- player_id
|
||||
- identifier
|
||||
- linkToProof
|
||||
- type
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: player
|
||||
table:
|
||||
name: Profile
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- id
|
||||
- name
|
||||
- description
|
||||
- url
|
||||
- xp
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: player
|
||||
table:
|
||||
name: Quest
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- quest_id
|
||||
- player_id
|
||||
- time
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: player
|
||||
table:
|
||||
name: Quest_Completed
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- player_id
|
||||
- startTime
|
||||
- endTime
|
||||
- xp
|
||||
computed_fields: []
|
||||
filter: {}
|
||||
limit: null
|
||||
role: player
|
||||
table:
|
||||
name: XPInterval
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- sentences
|
||||
filter:
|
||||
id:
|
||||
_eq: X-Hasura-User-Id
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: player
|
||||
table:
|
||||
name: Player
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
95
package-lock.json
generated
Normal file
95
package-lock.json
generated
Normal file
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"name": "@metafam/the-game",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"hasura-cli": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/hasura-cli/-/hasura-cli-1.1.1.tgz",
|
||||
"integrity": "sha512-mnqLdVLozScAQG91e/OgcJODC+TftlW9NRlJKXwGuG0emfqu7mtbo3D03P0bSKTMOwm7jgYzAbkLjnHexVmdyQ==",
|
||||
"requires": {
|
||||
"axios": "^0.19.0",
|
||||
"chalk": "^2.4.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@metafam/the-game",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"docker:start": "docker-compose up -d database && sleep 2 && docker-compose up --build -d",
|
||||
"docker:stop": "docker-compose down",
|
||||
"docker:clean": "docker-compose down -v",
|
||||
"hasura": "hasura --project ./hasura",
|
||||
"hasura:console": "npm run hasura console -- --no-browser",
|
||||
"hasura:migrate:init": "npm run hasura migrate create \"init\" -- --from-server",
|
||||
"hasura:migrate:apply": "npm run hasura migrate apply",
|
||||
"test": "cd packages/tests && yarn test && cd ../.."
|
||||
},
|
||||
"dependencies": {
|
||||
"hasura-cli": "1.1.1"
|
||||
}
|
||||
}
|
||||
23
packages/app-react/.gitignore
vendored
Normal file
23
packages/app-react/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
44
packages/app-react/README.md
Normal file
44
packages/app-react/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn start`
|
||||
|
||||
Runs the app in the development mode.<br />
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br />
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br />
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `yarn build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br />
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br />
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `yarn eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
50
packages/app-react/package.json
Normal file
50
packages/app-react/package.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@the-game/app-react",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.0.0-beta.43",
|
||||
"@apollo/react-hooks": "^3.1.5",
|
||||
"@material-ui/core": "^4.9.10",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react": "^16.9.0",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@walletconnect/web3-provider": "^1.0.0-beta.47",
|
||||
"apollo-boost": "^0.4.7",
|
||||
"ethers": "^4.0.46",
|
||||
"graphql": "^15.0.0",
|
||||
"graphql-codegen-hasura-core": "^4.8.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-scripts": "3.4.1",
|
||||
"typescript": "~3.7.2",
|
||||
"uuid": "^7.0.3",
|
||||
"web3": "^1.2.6",
|
||||
"web3modal": "^1.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
packages/app-react/public/favicon.ico
Normal file
BIN
packages/app-react/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
20
packages/app-react/public/index.html
Normal file
20
packages/app-react/public/index.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="The Meta Game"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>The Game</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
packages/app-react/public/logo192.png
Normal file
BIN
packages/app-react/public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
packages/app-react/public/logo512.png
Normal file
BIN
packages/app-react/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
packages/app-react/public/manifest.json
Normal file
25
packages/app-react/public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
packages/app-react/public/robots.txt
Normal file
3
packages/app-react/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
9
packages/app-react/src/App.test.tsx
Normal file
9
packages/app-react/src/App.test.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
const { getByText } = render(<App />);
|
||||
const linkElement = getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
23
packages/app-react/src/App.tsx
Normal file
23
packages/app-react/src/App.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { ApolloProvider } from '@apollo/react-hooks';
|
||||
|
||||
import { CssBaseline } from '@material-ui/core';
|
||||
import { createApolloClient } from './apollo';
|
||||
|
||||
import Home from './containers/Home';
|
||||
import Web3ContextProvider from './contexts/Web3';
|
||||
|
||||
const apolloClient = createApolloClient();
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<Web3ContextProvider>
|
||||
<CssBaseline/>
|
||||
<Home/>
|
||||
</Web3ContextProvider>
|
||||
</ApolloProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
72
packages/app-react/src/apollo/auth.js
Normal file
72
packages/app-react/src/apollo/auth.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import queries from '../graphql/queries';
|
||||
import { getSignerAddress } from '../lib/did';
|
||||
|
||||
const STORAGE_KEY = 'auth-token';
|
||||
|
||||
function getTokenFromStore() {
|
||||
return localStorage.getItem(STORAGE_KEY);
|
||||
}
|
||||
|
||||
function setTokenInStore(token) {
|
||||
return localStorage.setItem(STORAGE_KEY, token);
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
return localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
|
||||
export function loginLoading(client) {
|
||||
client.writeData({
|
||||
data: {
|
||||
authState: 'loading',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function login(client, token, ethAddress) {
|
||||
client.writeData({
|
||||
data: {
|
||||
authState: 'loading',
|
||||
authToken: token,
|
||||
},
|
||||
});
|
||||
setTokenInStore(token);
|
||||
|
||||
return client.query({
|
||||
query: queries.get_MyProfile,
|
||||
variables: { eth_address: ethAddress }
|
||||
})
|
||||
.then(async res => {
|
||||
if(res.data.Profile.length === 0) {
|
||||
throw new Error('Impossible to fetch player, not found.');
|
||||
}
|
||||
client.writeData({
|
||||
data: {
|
||||
authState: 'logged',
|
||||
playerId: res.data.Profile[0].Player.id,
|
||||
},
|
||||
});
|
||||
setTokenInStore(token);
|
||||
})
|
||||
.catch(async error => {
|
||||
client.writeData({
|
||||
data: {
|
||||
authState: 'error',
|
||||
authToken: null,
|
||||
},
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
clearToken();
|
||||
}
|
||||
|
||||
export function checkStoredAuth(client) {
|
||||
const token = getTokenFromStore();
|
||||
if(token) {
|
||||
const address = getSignerAddress(token);
|
||||
login(client, token, address).catch(console.error)
|
||||
}
|
||||
}
|
||||
58
packages/app-react/src/apollo/client.js
Normal file
58
packages/app-react/src/apollo/client.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import ApolloClient from 'apollo-boost';
|
||||
|
||||
import config from '../config';
|
||||
|
||||
import { localQueries, logout } from './index';
|
||||
import { checkStoredAuth } from './auth';
|
||||
|
||||
export function createApolloClient() {
|
||||
let client;
|
||||
|
||||
const defaultClientState = {
|
||||
authState: 'anonymous',
|
||||
authToken: null,
|
||||
playerId: null,
|
||||
};
|
||||
|
||||
async function authMiddleware(operation) {
|
||||
const queryLogin = client.readQuery({ query: localQueries.get_authState });
|
||||
|
||||
if(queryLogin.authToken) {
|
||||
operation.setContext({
|
||||
headers: {
|
||||
'authorization': `Bearer ${queryLogin.authToken}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onErrorMiddleware({ networkError = {}, graphQLErrors = {}, operation }) {
|
||||
if (networkError.statusCode === 401 || graphQLErrors[0]?.extensions?.code === 'invalid-jwt') {
|
||||
console.error('Authentication error, login out');
|
||||
logout(client);
|
||||
client.resetStore();
|
||||
}
|
||||
else {
|
||||
console.error('GraphQL request error:', networkError);
|
||||
}
|
||||
}
|
||||
|
||||
client = new ApolloClient({
|
||||
uri: config.graphqlURL,
|
||||
request: authMiddleware,
|
||||
onError: onErrorMiddleware,
|
||||
clientState: {
|
||||
defaults: defaultClientState,
|
||||
resolvers: {},
|
||||
},
|
||||
});
|
||||
|
||||
client.onResetStore(() => {
|
||||
client.writeData({ data: defaultClientState })
|
||||
});
|
||||
|
||||
checkStoredAuth(client);
|
||||
|
||||
return client;
|
||||
}
|
||||
7
packages/app-react/src/apollo/index.js
Normal file
7
packages/app-react/src/apollo/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createApolloClient } from './client';
|
||||
import { login, logout } from './auth';
|
||||
import * as localQueries from './localQueries';
|
||||
|
||||
export {
|
||||
localQueries, createApolloClient, login, logout,
|
||||
};
|
||||
9
packages/app-react/src/apollo/localQueries.js
Normal file
9
packages/app-react/src/apollo/localQueries.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { gql } from 'apollo-boost';
|
||||
|
||||
export const get_authState = gql`
|
||||
query AuthState {
|
||||
authState @client
|
||||
authToken @client
|
||||
playerId @client
|
||||
}
|
||||
`;
|
||||
11
packages/app-react/src/components/Player.tsx
Normal file
11
packages/app-react/src/components/Player.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
|
||||
export default function Player({ player }: { player: any }) {
|
||||
return (
|
||||
<Box>
|
||||
{player.id}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
4
packages/app-react/src/config.ts
Normal file
4
packages/app-react/src/config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
graphqlURL: process.env.GRAPHQL_URL || 'http://localhost:8080/v1/graphql',
|
||||
infuraId: process.env.INFURA_ID || '781d8466252d47508e177b8637b1c2fd',
|
||||
};
|
||||
21
packages/app-react/src/containers/Home.jsx
Normal file
21
packages/app-react/src/containers/Home.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
import { useQuery } from '@apollo/react-hooks';
|
||||
|
||||
import PlayerList from './PlayerList';
|
||||
import Login from './Login';
|
||||
import MyPlayer from './MyPlayer';
|
||||
import {localQueries} from "../apollo";
|
||||
|
||||
export default function Home() {
|
||||
const { data, loading } = useQuery(localQueries.get_authState);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PlayerList/>
|
||||
<Login/>
|
||||
{!loading && data?.authState === 'logged' && <MyPlayer/>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
39
packages/app-react/src/containers/Login.tsx
Normal file
39
packages/app-react/src/containers/Login.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React, {useContext} from 'react';
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
|
||||
import { Web3Context } from '../contexts/Web3';
|
||||
import {localQueries} from "../apollo";
|
||||
import { useQuery } from '@apollo/react-hooks';
|
||||
|
||||
export default function Login() {
|
||||
const { data, loading } = useQuery(localQueries.get_authState);
|
||||
|
||||
const { connectWeb3 } = useContext(Web3Context);
|
||||
|
||||
if(loading || data?.authState === 'loading') {
|
||||
return (
|
||||
<Box>
|
||||
Connecting...
|
||||
</Box>
|
||||
);
|
||||
} else if(data?.authState === 'logged') {
|
||||
return (
|
||||
<Box>Connected</Box>
|
||||
);
|
||||
} else if(data?.authState === 'error') {
|
||||
return (
|
||||
<Box>
|
||||
Connection error
|
||||
</Box>
|
||||
);
|
||||
} else if(data?.authState === 'anonymous') {
|
||||
return (
|
||||
<Box>
|
||||
<button onClick={connectWeb3}>Connect</button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return 'Unknown state'
|
||||
}
|
||||
25
packages/app-react/src/containers/MyPlayer.tsx
Normal file
25
packages/app-react/src/containers/MyPlayer.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
|
||||
import Player from '../components/Player';
|
||||
import { useMyPlayer } from '../graphql/hooks';
|
||||
|
||||
export default function MyPlayer() {
|
||||
const { data, called, loading, error } = useMyPlayer();
|
||||
|
||||
if(error) {
|
||||
return <div>error</div>
|
||||
}
|
||||
if(loading || !called) {
|
||||
return <div>loading</div>
|
||||
}
|
||||
|
||||
const myPlayer = data.Player[0];
|
||||
return (
|
||||
<Box>
|
||||
<h4>My player</h4>
|
||||
<Player player={myPlayer} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
25
packages/app-react/src/containers/PlayerList.tsx
Normal file
25
packages/app-react/src/containers/PlayerList.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
|
||||
import {useQuery} from '@apollo/react-hooks';
|
||||
|
||||
import queries from '../graphql/queries';
|
||||
|
||||
import Player from '../components/Player';
|
||||
|
||||
export default function PlayerList() {
|
||||
const { data, loading } = useQuery(queries.get_Player);
|
||||
|
||||
if(loading) {
|
||||
return <div>loading</div>
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<h4>Player list</h4>
|
||||
{data.Player.map((player: any) =>
|
||||
<Player key={player.id} player={player} />
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
71
packages/app-react/src/contexts/Web3.tsx
Normal file
71
packages/app-react/src/contexts/Web3.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, {createContext, useCallback, useState} from 'react';
|
||||
import WalletConnectProvider from '@walletconnect/web3-provider';
|
||||
import Web3Modal from 'web3modal';
|
||||
import Web3 from 'web3';
|
||||
import { ethers } from 'ethers';
|
||||
import {AsyncSendable} from 'ethers/providers';
|
||||
import {useApolloClient} from '@apollo/react-hooks';
|
||||
|
||||
import config from '../config';
|
||||
import {createToken} from '../lib/did';
|
||||
import {loginLoading, login} from '../apollo/auth';
|
||||
|
||||
export const Web3Context = createContext({
|
||||
ethersProvider: null,
|
||||
connectWeb3: () => {},
|
||||
});
|
||||
|
||||
const providerOptions = {
|
||||
walletconnect: {
|
||||
package: WalletConnectProvider,
|
||||
options: {
|
||||
infuraId: config.infuraId,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const web3Modal = new Web3Modal({
|
||||
network: 'mainnet',
|
||||
cacheProvider: true,
|
||||
providerOptions,
|
||||
});
|
||||
|
||||
|
||||
const Web3ContextProvider = props => {
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const [ethersProvider, setEthersProvider] = useState(null);
|
||||
|
||||
const connectWeb3 = useCallback(async () => {
|
||||
|
||||
loginLoading(apolloClient);
|
||||
|
||||
try {
|
||||
|
||||
const provider = await web3Modal.connect();
|
||||
|
||||
const web3Provider = new Web3(provider);
|
||||
const ethersProvider = new ethers.providers.Web3Provider(web3Provider.currentProvider as AsyncSendable);
|
||||
const signer = ethersProvider.getSigner();
|
||||
const address = await signer.getAddress();
|
||||
|
||||
const token = await createToken(ethersProvider);
|
||||
console.log(token);
|
||||
|
||||
await login(apolloClient, token, address);
|
||||
setEthersProvider(ethersProvider);
|
||||
|
||||
} catch(error) {
|
||||
console.error('impossible to connect', error);
|
||||
}
|
||||
}, [apolloClient]);
|
||||
|
||||
return (
|
||||
<Web3Context.Provider value={{ ethersProvider, connectWeb3 }}>
|
||||
{props.children}
|
||||
</Web3Context.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Web3ContextProvider;
|
||||
14
packages/app-react/src/graphql/fragments.ts
Normal file
14
packages/app-react/src/graphql/fragments.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const fragments: any = {};
|
||||
|
||||
fragments.PlayerFragment = `
|
||||
fragment PlayerFragment on Player {
|
||||
id
|
||||
}
|
||||
`;
|
||||
fragments.ProfileFragment = `
|
||||
fragment ProfileFragment on Profile {
|
||||
identifier
|
||||
}
|
||||
`;
|
||||
|
||||
export default fragments;
|
||||
25
packages/app-react/src/graphql/hooks.ts
Normal file
25
packages/app-react/src/graphql/hooks.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useQuery, useLazyQuery } from '@apollo/react-hooks';
|
||||
|
||||
import { localQueries } from '../apollo';
|
||||
import queries from './queries';
|
||||
|
||||
export function useMyPlayer() {
|
||||
const authStateQuery = useQuery(localQueries.get_authState);
|
||||
const [getMyPlayer, myPlayerQuery] = useLazyQuery(queries.get_MyPlayer);
|
||||
|
||||
const playerId = authStateQuery.data?.playerId;
|
||||
|
||||
useEffect(() => {
|
||||
if(playerId) {
|
||||
getMyPlayer({
|
||||
variables: {
|
||||
player_id: playerId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [playerId]);
|
||||
|
||||
return myPlayerQuery;
|
||||
}
|
||||
45
packages/app-react/src/graphql/queries.ts
Normal file
45
packages/app-react/src/graphql/queries.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import fragments from './fragments';
|
||||
|
||||
const queries: any = {};
|
||||
|
||||
queries.get_Player = gql`
|
||||
query GetPlayer {
|
||||
Player {
|
||||
...PlayerFragment
|
||||
}
|
||||
}
|
||||
${fragments.PlayerFragment}
|
||||
`;
|
||||
|
||||
queries.get_MyPlayer = gql`
|
||||
query GetPlayer($player_id: uuid) {
|
||||
Player(
|
||||
where: { id: { _eq: $player_id } }
|
||||
) {
|
||||
...PlayerFragment
|
||||
}
|
||||
}
|
||||
${fragments.PlayerFragment}
|
||||
`;
|
||||
|
||||
queries.get_MyProfile = gql`
|
||||
query GetMyProfile($eth_address: String) {
|
||||
Profile(
|
||||
where: {
|
||||
identifier: { _eq: $eth_address },
|
||||
type: { _eq: "ETHEREUM" }
|
||||
}
|
||||
) {
|
||||
...ProfileFragment
|
||||
Player {
|
||||
...PlayerFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${fragments.PlayerFragment}
|
||||
${fragments.ProfileFragment}
|
||||
`;
|
||||
|
||||
export default queries;
|
||||
11
packages/app-react/src/index.tsx
Normal file
11
packages/app-react/src/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
serviceWorker.unregister();
|
||||
39
packages/app-react/src/lib/did.ts
Normal file
39
packages/app-react/src/lib/did.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ethers } from "ethers";
|
||||
|
||||
const tokenDuration = 1000 * 60 * 60 * 24 * 7; // 7 days
|
||||
|
||||
export async function createToken(provider) {
|
||||
const signer = provider.getSigner();
|
||||
const address = await signer.getAddress();
|
||||
|
||||
const iat = +new Date();
|
||||
|
||||
const claim = {
|
||||
iat: +new Date(),
|
||||
exp: iat + tokenDuration,
|
||||
iss: address,
|
||||
aud: 'the-game',
|
||||
tid: uuidv4(),
|
||||
};
|
||||
|
||||
const serializedClaim = JSON.stringify(claim);
|
||||
const proof = await signer.signMessage(serializedClaim);
|
||||
|
||||
const DIDToken = btoa(JSON.stringify([proof, serializedClaim]));
|
||||
|
||||
return DIDToken;
|
||||
}
|
||||
|
||||
|
||||
export function getSignerAddress(token: string): any {
|
||||
try {
|
||||
const rawToken = atob(token);
|
||||
const [proof, rawClaim] = JSON.parse(rawToken);
|
||||
const signerAddress = ethers.utils.verifyMessage(rawClaim, proof);
|
||||
return signerAddress;
|
||||
} catch (e) {
|
||||
console.error('Token verification failed', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
1
packages/app-react/src/react-app-env.d.ts
vendored
Normal file
1
packages/app-react/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
149
packages/app-react/src/serviceWorker.ts
Normal file
149
packages/app-react/src/serviceWorker.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
type Config = {
|
||||
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
||||
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
||||
};
|
||||
|
||||
export function register(config?: Config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(
|
||||
process.env.PUBLIC_URL,
|
||||
window.location.href
|
||||
);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl: string, config?: Config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { 'Service-Worker': 'script' }
|
||||
})
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready
|
||||
.then(registration => {
|
||||
registration.unregister();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
5
packages/app-react/src/setupTests.ts
Normal file
5
packages/app-react/src/setupTests.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
25
packages/app-react/tsconfig.json
Normal file
25
packages/app-react/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
13187
packages/app-react/yarn.lock
Normal file
13187
packages/app-react/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
2
packages/backend/.dockerignore
Normal file
2
packages/backend/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
1
packages/backend/.gitignore
vendored
Normal file
1
packages/backend/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
dist
|
||||
13
packages/backend/Dockerfile
Normal file
13
packages/backend/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM node:12
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
CMD [ "npm", "start" ]
|
||||
460
packages/backend/package-lock.json
generated
Normal file
460
packages/backend/package-lock.json
generated
Normal file
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"name": "@the-game/backend",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
|
||||
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/express": {
|
||||
"version": "4.17.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz",
|
||||
"integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/qs": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"@types/express-serve-static-core": {
|
||||
"version": "4.17.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.5.tgz",
|
||||
"integrity": "sha512-578YH5Lt88AKoADy0b2jQGwJtrBxezXtVe/MBqWXKZpqx91SnC0pVkVCcxcytz3lWW+cHBYDi3Ysh0WXc+rAYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
|
||||
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "13.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz",
|
||||
"integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/qs": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz",
|
||||
"integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/range-parser": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
|
||||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
|
||||
"integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/mime": "*"
|
||||
}
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"esm": {
|
||||
"version": "3.2.25",
|
||||
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
|
||||
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.17.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.7",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.1.2",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.5",
|
||||
"qs": "6.7.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.17.1",
|
||||
"serve-static": "1.14.1",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": "~1.5.0",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~1.5.0",
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.43.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
|
||||
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.26",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
|
||||
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
|
||||
"requires": {
|
||||
"mime-db": "1.43.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.7.2",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.1",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.17.1"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
|
||||
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
|
||||
"dev": true
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
}
|
||||
}
|
||||
}
|
||||
23
packages/backend/package.json
Normal file
23
packages/backend/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@the-game/backend",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "node ./dist/index.js",
|
||||
"build": "tsc"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"ethers": "^4.0.46",
|
||||
"express": "^4.17.1",
|
||||
"node-fetch": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/node": "^13.11.1",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
5
packages/backend/src/config.ts
Normal file
5
packages/backend/src/config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
port: process.env.PORT || 3000,
|
||||
graphqlURL: process.env.GRAPHQL_URL || 'http://localhost:8080/v1/graphql',
|
||||
adminKey: process.env.HASURA_GRAPHQL_ADMIN_SECRET || 'metagame_secret',
|
||||
};
|
||||
18
packages/backend/src/handlers/auth-webhook/did.ts
Normal file
18
packages/backend/src/handlers/auth-webhook/did.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ethers } from "ethers";
|
||||
|
||||
export function verifyToken(token: string): any {
|
||||
try {
|
||||
const rawToken = Buffer.from(token, 'base64').toString();
|
||||
const [proof, rawClaim] = JSON.parse(rawToken);
|
||||
const claim = JSON.parse(rawClaim);
|
||||
|
||||
const signerAddress = ethers.utils.verifyMessage(rawClaim, proof);
|
||||
if(signerAddress !== claim.iss) {
|
||||
return null;
|
||||
}
|
||||
return claim;
|
||||
} catch (e) {
|
||||
console.error('Token verification failed', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
45
packages/backend/src/handlers/auth-webhook/handler.ts
Normal file
45
packages/backend/src/handlers/auth-webhook/handler.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { verifyToken } from './did';
|
||||
import { getPlayer } from './users';
|
||||
|
||||
const unauthorizedVariables = {
|
||||
'X-Hasura-Role': 'public',
|
||||
};
|
||||
|
||||
function getHeaderToken(req: Request): string | null {
|
||||
const authHeader = req.headers['authorization'];
|
||||
if(!authHeader) return null;
|
||||
const token = authHeader.replace('Bearer ', '');
|
||||
if(token.length === 0) return null;
|
||||
return token;
|
||||
}
|
||||
|
||||
const handler = async (req: Request, res: Response) => {
|
||||
|
||||
const token = getHeaderToken(req);
|
||||
|
||||
if(!token) {
|
||||
res.json(unauthorizedVariables);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
|
||||
const claim = verifyToken(token);
|
||||
if(!claim) {
|
||||
res.status(401).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const player = await getPlayer(claim.iss);
|
||||
|
||||
const hasuraVariables = {
|
||||
'X-Hasura-Role': 'player',
|
||||
'X-Hasura-User-Id': player.id,
|
||||
};
|
||||
res.json(hasuraVariables);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default handler;
|
||||
94
packages/backend/src/handlers/auth-webhook/users.ts
Normal file
94
packages/backend/src/handlers/auth-webhook/users.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import config from '../../config';
|
||||
|
||||
const getPlayerQuery = `
|
||||
query GetPlayerFromETH ($eth_address: String) {
|
||||
Profile(
|
||||
where: {
|
||||
identifier: { _eq: $eth_address },
|
||||
type: { _eq: "ETHEREUM" }
|
||||
}
|
||||
) {
|
||||
Player {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const createPlayerMutation = `
|
||||
mutation CreatePlayer {
|
||||
insert_Player(objects: {}) {
|
||||
returning {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const createProfileMutation = `
|
||||
mutation CreateProfileFromETH ($player_id: uuid, $eth_address: String) {
|
||||
insert_Profile(
|
||||
objects: {
|
||||
player_id: $player_id,
|
||||
type: "ETHEREUM",
|
||||
identifier: $eth_address
|
||||
}) {
|
||||
returning {
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
async function hasuraQuery(query: string, qv: any = {}) {
|
||||
const result = await fetch(config.graphqlURL, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ query: query, variables: qv }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-hasura-access-key': config.adminKey,
|
||||
},
|
||||
});
|
||||
|
||||
const { errors, data } = await result.json();
|
||||
|
||||
if(errors) {
|
||||
throw new Error(JSON.stringify(errors));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
interface IPlayer {
|
||||
id: string
|
||||
}
|
||||
|
||||
export async function createPlayer(ethAddress: string): Promise<IPlayer> {
|
||||
const resPlayer = await hasuraQuery(createPlayerMutation );
|
||||
const player = resPlayer.insert_Player.returning[0];
|
||||
|
||||
await hasuraQuery(createProfileMutation, {
|
||||
player_id: player.id,
|
||||
eth_address: ethAddress,
|
||||
});
|
||||
|
||||
// TODO do it in only one query
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
export async function getPlayer(ethAddress: string): Promise<IPlayer> {
|
||||
const res = await hasuraQuery(getPlayerQuery, {
|
||||
eth_address: ethAddress,
|
||||
});
|
||||
|
||||
let player = res.Profile[0]?.Player;
|
||||
|
||||
if(!player) {
|
||||
// TODO if two requests sent at the same time, collision
|
||||
player = await createPlayer(ethAddress);
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
15
packages/backend/src/handlers/routes.ts
Normal file
15
packages/backend/src/handlers/routes.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import express from 'express';
|
||||
|
||||
import { asyncHandlerWrapper } from '../lib/apiHelpers';
|
||||
|
||||
import authHandler from './auth-webhook/handler';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', function (req, res) {
|
||||
res.send('pong')
|
||||
});
|
||||
|
||||
router.get('/auth-webhook', asyncHandlerWrapper(authHandler));
|
||||
|
||||
export default router;
|
||||
19
packages/backend/src/index.ts
Normal file
19
packages/backend/src/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
|
||||
import config from './config';
|
||||
|
||||
import routes from './handlers/routes';
|
||||
import { errorMiddleware } from './lib/apiHelpers';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(routes);
|
||||
|
||||
app.use(errorMiddleware);
|
||||
|
||||
app.listen(config.port, function () {
|
||||
console.log(`Listening on port ${config.port}`)
|
||||
});
|
||||
17
packages/backend/src/lib/apiHelpers.ts
Normal file
17
packages/backend/src/lib/apiHelpers.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export function asyncHandlerWrapper(middleware: any) {
|
||||
if (middleware.length === 4) {
|
||||
return function wrappedHandler(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
middleware(error, req, res, next).catch(next);
|
||||
};
|
||||
}
|
||||
return function wrappedHandler(req: Request, res: Response, next: NextFunction) {
|
||||
middleware(req, res, next).catch(next);
|
||||
};
|
||||
}
|
||||
|
||||
export function errorMiddleware(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
console.error(error);
|
||||
res.status(500).send('Unexpected error');
|
||||
}
|
||||
11
packages/backend/tsconfig.json
Normal file
11
packages/backend/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"strict": false,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
1
packages/graphql-codegen/.gitignore
vendored
Normal file
1
packages/graphql-codegen/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
autogen
|
||||
7
packages/graphql-codegen/customFragments.ts
Normal file
7
packages/graphql-codegen/customFragments.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const Player = gql`
|
||||
fragment Player on Player {
|
||||
id
|
||||
}
|
||||
`;
|
||||
26
packages/graphql-codegen/graphql-codegen-gql.yaml
Normal file
26
packages/graphql-codegen/graphql-codegen-gql.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
schema:
|
||||
- http://localhost:8080/v1/graphql
|
||||
documents:
|
||||
overwrite: true
|
||||
config:
|
||||
scalars:
|
||||
DateTime: Date
|
||||
JSON: "{ [key: string]: any }"
|
||||
generates:
|
||||
./autogen/hasura/gql.ts:
|
||||
plugins:
|
||||
- graphql-codegen-hasura-gql
|
||||
documents:
|
||||
- ./customFragments.ts
|
||||
config:
|
||||
reactApolloVersion: 3
|
||||
typescriptCodegenOutputPath: ../
|
||||
trimString:
|
||||
withQueries: true
|
||||
withSubscriptions: false
|
||||
withInserts: false
|
||||
withUpdates: false
|
||||
withDeletes: false
|
||||
./autogen/graphql.schema.json:
|
||||
plugins:
|
||||
- introspection
|
||||
66
packages/graphql-codegen/graphql-codegen-typescript.yaml
Normal file
66
packages/graphql-codegen/graphql-codegen-typescript.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
schema:
|
||||
- ./autogen/graphql.schema.json
|
||||
overwrite: true
|
||||
config:
|
||||
scalars:
|
||||
DateTime: Date
|
||||
JSON: "{ [key: string]: any }"
|
||||
generates:
|
||||
./autogen/index.tsx:
|
||||
documents:
|
||||
- ./customFragments.ts
|
||||
- ./autogen/hasura/gql.ts
|
||||
plugins:
|
||||
- typescript
|
||||
- typescript-operations
|
||||
- typescript-react-apollo
|
||||
config:
|
||||
reactApolloVersion: 3
|
||||
withHooks: false
|
||||
withHOC: false
|
||||
withComponent: false
|
||||
skipTypename: false
|
||||
includeDirectives: true
|
||||
./autogen/hasura/ts.ts:
|
||||
documents:
|
||||
- ./customFragments.ts
|
||||
plugins:
|
||||
- graphql-codegen-hasura-typescript
|
||||
config:
|
||||
reactApolloVersion: 3
|
||||
typescriptCodegenOutputPath: "../"
|
||||
trimString:
|
||||
withClientAndCacheHelpers: true
|
||||
withQueries: true
|
||||
withSubscriptions: false
|
||||
withInserts: false
|
||||
withUpdates: false
|
||||
withDeletes: false
|
||||
./autogen/hasura/react.ts:
|
||||
documents:
|
||||
- ./customFragments.ts
|
||||
plugins:
|
||||
- graphql-codegen-hasura-react
|
||||
config:
|
||||
reactApolloVersion: 3
|
||||
typescriptCodegenOutputPath: "../"
|
||||
trimString:
|
||||
withQueries: true
|
||||
withSubscriptions: false
|
||||
withInserts: false
|
||||
withUpdates: false
|
||||
withDeletes: false
|
||||
./autogen/hasura/config.ts:
|
||||
documents:
|
||||
- ./customFragments.ts
|
||||
plugins:
|
||||
- graphql-codegen-hasura-client-config
|
||||
config:
|
||||
reactApolloVersion: 3
|
||||
typescriptCodegenOutputPath: "../"
|
||||
trimString:
|
||||
withTypePolicies: true
|
||||
withResolverTypes: true
|
||||
withCombinedTypePolicyObject: false
|
||||
withCacheRedirects: true
|
||||
withCombinedCacheRedirectObject: true
|
||||
22
packages/graphql-codegen/package.json
Normal file
22
packages/graphql-codegen/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@the-game/graphql-codegen",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"generate": "graphql-codegen --config=graphql-codegen-gql.yaml; graphql-codegen --config=graphql-codegen-typescript.yaml"
|
||||
},
|
||||
"dependencies": {
|
||||
"@graphql-codegen/cli": "^1.13.2",
|
||||
"@graphql-codegen/introspection": "^1.13.2",
|
||||
"@graphql-codegen/typescript": "^1.13.2",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.2",
|
||||
"@graphql-codegen/typescript-react-apollo": "^1.13.2",
|
||||
"graphql-codegen-hasura-client-config": "^4.8.3",
|
||||
"graphql-codegen-hasura-core": "^4.8.3",
|
||||
"graphql-codegen-hasura-gql": "^4.8.3",
|
||||
"graphql-codegen-hasura-react": "^4.8.3",
|
||||
"graphql-codegen-hasura-shared": "^4.8.3",
|
||||
"graphql-codegen-hasura-typescript": "^4.8.3"
|
||||
}
|
||||
}
|
||||
4439
packages/graphql-codegen/yarn.lock
Normal file
4439
packages/graphql-codegen/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user