Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
NODE_ENV=development
DOCKER_HOST_URL=http://host.docker.internal
HASURA_SECRET=secret
HASURA_GRAPHQL_AUTH_HOOK=http://localhost:3000/hasura
POSTGRES_USER=postgres
POSTGRES_PASSWORD=test
POSTGRES_DB=gameofblocks_dev
Expand All @@ -15,5 +16,4 @@ AUTH0_CLIENT_SECRET=123456
AUTH0_CALLBACK_URL=http://localhost:3000/callback
SENDGRID_API_KEY=123456
BASE_URL=http://localhost:3000
HASURA_JWT_SECRET=
GRAPHQL_SERVER_URI=http://localhost:5000
2 changes: 2 additions & 0 deletions .k8s/hasura/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ spec:
value: ${HASURA_SECRET}
- name: HASURA_GRAPHQL_ENABLE_CONSOLE
value: 'true'
- name: HASURA_GRAPHQL_AUTH_HOOK
value: gameofblocks-${CI_ENVIRONMENT_SLUG}/hasura
ports:
- name: http-metrics
protocol: TCP
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ services:
HASURA_GRAPHQL_DATABASE_URL: postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@db:5432/$POSTGRES_DB
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
HASURA_GRAPHQL_ADMIN_SECRET: $HASURA_SECRET
HASURA_GRAPHQL_JWT_SECRET: $HASURA_JWT_SECRET
HASURA_GRAPHQL_AUTH_HOOK: $HASURA_GRAPHQL_AUTH_HOOK

volumes:
gameofblocks-pgdata:
33 changes: 33 additions & 0 deletions packages/app/components/profile/Profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { FunctionComponent } from 'react';
import { useQuery } from '@apollo/react-hooks';

import { USER, UserQueryProps } from './queries';

export const Profile: FunctionComponent = () => {
const { data, error, loading } = useQuery<UserQueryProps>(USER);
if (error) {
return <div>{error.message}</div>;
}

if (loading) {
return <div>Chargement...</div>;
}

const [user] = data.user;
const lastLoginDate = new Date(user.last_login);

return (
<div>
{user.picture && <img src={user.picture} alt="avatar" />}
<div>{`Email: ${user.email}`}</div>
<div>
{`Last login: ${lastLoginDate.toLocaleDateString()} ${lastLoginDate.toLocaleTimeString()}`}
</div>
<a href="/logout">
<button type="button">logout</button>
</a>
</div>
);
};

export default Profile;
1 change: 1 addition & 0 deletions packages/app/components/profile/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Profile } from './Profile';
19 changes: 19 additions & 0 deletions packages/app/components/profile/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import gql from 'graphql-tag';

export interface UserQueryProps {
user: {
picture: string;
email: string;
last_login: string;
}[];
}

export const USER = gql`
query user {
user {
picture
email
last_login
}
}
`;
59 changes: 59 additions & 0 deletions packages/app/lib/init-apollo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
ApolloClient,
NormalizedCacheObject,
InMemoryCache,
} from 'apollo-boost';
import { createHttpLink } from 'apollo-link-http';
import fetch from 'isomorphic-unfetch';
import { InitApolloOptions } from 'next-with-apollo';

const { GRAPHQL_SERVER_URI } = process.env;

let apolloClient = null;

function create(
options: InitApolloOptions<any>
): ApolloClient<NormalizedCacheObject> {
const isBrowser = typeof window !== 'undefined';

const enchancedFetch = async (url, init): Promise<Response> => {
const response = await fetch(url, {
...init,
headers: {
...init.headers,
Cookie: options.ctx.req.headers.cookie,
},
});
return response;
};

return new ApolloClient({
connectToDevTools: isBrowser,
ssrMode: !isBrowser,
link: createHttpLink({
fetch: !isBrowser ? enchancedFetch : fetch,
uri: GRAPHQL_SERVER_URI,
credentials: 'include',
}),
cache: new InMemoryCache().restore(options.initialState || {}),
});
}

export function initApollo(
options: InitApolloOptions<any>
): ApolloClient<NormalizedCacheObject> {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return create(options);
}

// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(options);
}

return apolloClient;
}

export default initApollo;
9 changes: 4 additions & 5 deletions packages/app/next.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
require('dotenv').config();

const env = require('@gameofblocks/env');
const webpack = require('webpack');

module.exports = {
publicRuntimeConfig: {
GRAPHQL_SERVER_URI: process.env.GRAPHQL_SERVER_URI,
NODE_ENV: process.env.NODE_ENV,
GRAPHQL_SERVER_URI: env.GRAPHQL_SERVER_URI,
NODE_ENV: env.NODE_ENV,
},
webpack: (config) => {
config.plugins.push(new webpack.EnvironmentPlugin(process.env));
config.plugins.push(new webpack.EnvironmentPlugin(env));
return config;
},
};
2 changes: 2 additions & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"lint:fix": "yarn lint --fix"
},
"dependencies": {
"@apollo/react-hooks": "^3.1.4",
"@gameofblocks/env": "^1.0.0",
"@rebass/forms": "4.0.6",
"@rebass/preset": "4.0.5",
Expand All @@ -36,6 +37,7 @@
"memorystore": "1.6.2",
"next": "9.3.4",
"next-cookies": "2.0.3",
"next-with-apollo": "^5.0.0",
"passport": "0.4.1",
"passport-auth0": "1.3.2",
"pino": "6.1.1",
Expand Down
29 changes: 21 additions & 8 deletions packages/app/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable react/jsx-props-no-spreading */
import React from 'react';
import { AppProps } from 'next/app';
import { NextPage } from 'next';
import { ApolloProvider } from 'react-apollo';
import withApollo, { WithApolloProps } from 'next-with-apollo';
import { initApollo } from '../lib/init-apollo';

const GameOfBlocks: NextPage<AppProps> = ({
Component,
pageProps,
}: AppProps) => {
return <Component {...pageProps} />;
};
interface MainComponentProps extends WithApolloProps<any>, AppProps {}

export default GameOfBlocks;
class GameOfBlocks extends React.Component<MainComponentProps> {
static async getInitialProps() {
return {};
}

render() {
const { Component, pageProps, apollo } = this.props;
return (
<ApolloProvider client={apollo}>
<Component {...pageProps} />
</ApolloProvider>
);
}
}

export default withApollo(initApollo)(GameOfBlocks);
9 changes: 9 additions & 0 deletions packages/app/pages/profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React, { FunctionComponent } from 'react';

import { Profile } from '../components/profile';

const ProfilePage: FunctionComponent = () => {
return <Profile />;
};

export default ProfilePage;
1 change: 1 addition & 0 deletions packages/app/server/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { GRAPHQL_SERVER_URI, HASURA_SECRET } = env;

export const client = new ApolloClient({
uri: GRAPHQL_SERVER_URI,

headers: {
'x-hasura-admin-secret': HASURA_SECRET,
},
Expand Down
1 change: 1 addition & 0 deletions packages/app/server/graphql/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import gql from 'graphql-tag';
export const GET_USER = gql`
query user($authId: String!) {
user(limit: 1, where: { auth_id: { _eq: $authId } }) {
id
auth_id
email
picture
Expand Down
11 changes: 3 additions & 8 deletions packages/app/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import nextJs from 'next';
import { createServer } from 'http';
import expressPinoLogger from 'express-pino-logger';
import uid from 'uid-safe';
import session from 'express-session';
import cors from 'cors';
import passport from 'passport';
import Auth0Strategy from 'passport-auth0';
import MemoryStore from 'memorystore';
import env from '@gameofblocks/env';
import session from 'express-session';

import { logger } from '../utils/logger';
import { handleError } from '../utils/error-handler';
import authRoutes from './routes/auth';
import store, { MAX_AGE } from './session-store';

const dev = env.NODE_ENV !== 'production';
const app = nextJs({ dev });
Expand All @@ -25,17 +25,12 @@ const handle = app.getRequestHandler();
await app.prepare();
const server = express();

const SessionMemoryStore = MemoryStore(session);
const MAX_AGE = 86400000; // prune expired entries every 24h

const sessionConfig = {
secret: uid.sync(18),
cookie: {
maxAge: MAX_AGE,
},
store: new SessionMemoryStore({
checkPeriod: MAX_AGE,
}),
store,
resave: false,
saveUninitialized: true,
};
Expand Down
17 changes: 13 additions & 4 deletions packages/app/server/models/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,32 @@ export interface UserQueryVariables {
authId: string;
}

export interface UserCreationVariables {
export interface CreateUserVariables {
email: string;
picture: string;
authId: string;
}

export interface CreateUserMutationResult {
insert_user: {
affected_rows: number;
returning: User[];
};
}

export interface UpdateLastLoginVariables {
authId: string;
lastLogin: string;
}

export interface UserMutationResult {
affected_rows: number;
returning: User[];
export interface Auth0User {
auth_id: string;
email: string;
picture: string;
}

export interface User {
id: string;
auth_id: string;
email: string;
picture: string;
Expand Down
27 changes: 17 additions & 10 deletions packages/app/server/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { CREATE_USER, UPDATE_LAST_LOGIN } from '../graphql/mutations';
import {
UserQueryResult,
UserQueryVariables,
UserMutationResult,
UserCreationVariables,
CreateUserMutationResult,
CreateUserVariables,
UpdateLastLoginVariables,
User,
Auth0User,
} from './types';
import { logger } from '../../utils/logger';

async function find(authId: string): Promise<User> {
export async function find(authId: string): Promise<User> {
const { data } = await client.query<UserQueryResult, UserQueryVariables>({
query: GET_USER,
variables: { authId },
Expand All @@ -19,17 +21,20 @@ async function find(authId: string): Promise<User> {
return data ? data.user[0] : null;
}

async function create(userToCreate: User): Promise<User> {
async function create(userToCreate: Auth0User): Promise<User> {
const { email, picture, auth_id: authId } = userToCreate;
const { data } = await client.mutate<
UserMutationResult,
UserCreationVariables
CreateUserMutationResult,
CreateUserVariables
>({
mutation: CREATE_USER,
variables: { email, picture, authId },
});

const [user] = data.returning;
const {
insert_user: { returning },
} = data;
const [user] = returning;
return user;
}

Expand All @@ -43,12 +48,14 @@ async function updateLastLogin(authId: string): Promise<void> {
});
}

export async function loginUser(userToTest: User): Promise<User> {
const { auth_id: authId } = userToTest;
export async function loginUser(auth0User: Auth0User): Promise<User> {
const { auth_id: authId } = auth0User;
let user = await find(authId);
if (!user) {
user = await create(userToTest);
logger.info(`🚫 User ${authId} does not exist. User creation attempt...`);
user = await create(auth0User);
} else {
logger.info(`👋 User ${authId} logged in successfully`);
await updateLastLogin(authId);
}
return user;
Expand Down
Loading