nhost / hasura-backend-plus

🔑Auth and 📦Storage for Hasura. The quickest way to get Auth and Storage working for your next app based on Hasura.
https://nhost.github.io/hasura-backend-plus/
MIT License
1.17k stars 187 forks source link

Subscriber Check Feature - Frontend & Backend #670

Open fullakingi opened 2 years ago

fullakingi commented 2 years ago

Hi @elitan - made progress on a feature for nhost that checks if an authorised user is on a table called subscribers.

  1. chose project at nhost back-end

  2. create a table called subscribed with the following fields:

    • id (UUID)
    • created_at (timestamp)
    • email (text)
    • subscribed(Boolean)
  3. to populate this table with subscribers we use mailchimp API. we set up a webhook " api-xxxxxx.nhost.app"

I am attaching the code for the back-end API:

it is in root/API/mailchimp

const http = require('https');

const graphQL = (query, variables) => {

  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-hasura-admin-secret': process.env.NHOST_HASURA_ADMIN_SECRET     
    },
  };

  return new Promise((resolve, reject) => {
    const request = http.request(
      process.env.NHOST_HASURA_URL,
      options,
      res => {
        if (res.statusCode !== 200) {
          res.resume();
          reject(res.statusMessage);
        }
        res.setEncoding('utf8');
        let rawData = '';
        res.on('data', chunk => {rawData += chunk})
        res.on('end', () => {
          if (!res.complete) {
            reject('Aborted');
          }
          resolve(JSON.parse(rawData));
        });
      }
    );
    request.on('error', reject);
    request.write(JSON.stringify({
      query,
      variables
    }));
    request.end();
  });
};

module.exports = (req, res) => {
  if (req.body && req.body.type && req.body.data && req.body.data.email) {
    graphQL(`mutation ($email: String, $subscribed: Boolean) {
      insert_subscribed_one(
        object: {
          email: $email,
          subscribed: $subscribed
        }, on_conflict: {
          constraint: subscribed_email_key,
          update_columns: [subscribed]
        }) {
        id
      }
    }`, {
      email: req.body.data.email,
      subscribed: req.body.type === 'subscribe'
    }).then((data) => {
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.end(JSON.stringify(data));
    }).catch(err => {
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.end(err.toString());
    })
  } else {
    res.writeHead(200,  { 'Content-Type': 'text/plain' });
    res.end('Bad request data');
  }
} 

in the frontend (next js), we change the private route:

/* eslint-disable react-hooks/rules-of-hooks */
import { useRouter } from 'next/router'
import { useAuth } from '@nhost/react-auth'
import Image from 'next/image'
import { useQuery, gql } from '@apollo/client'
import { nhost } from '../utils/nhost'

const IS_SUBSCRIBED = gql`
query ($email: String) {
  subscribed(where: {
    email: {_eq: $email}
    subscribed: {_eq: true}
  } ) {
    subscribed
  }
}
`;

function PrivateRouteInternal(props) {
  const router = useRouter()
  const { loading, data } = useQuery(
    IS_SUBSCRIBED,
    {
      variables: {
        email: nhost.auth.user().email
      }
    }
  );
  console.log(nhost.auth.user().email);
  if (loading) {
    return <div className="flex items-center justify-center">
      <p className="text-sm">2. Checking registration status...</p>
      <div>
        <Image
          src="/loading.svg"
          alt="loading icon"
          width={100}
          height={100}
        />
      </div>
    </div>
  }
  console.log(JSON.stringify(data));

  if (!(data && data.subscribed && data.subscribed[0])) {
    setTimeout(() => { router.push('/onboard') }, 3000);
    return <div className="flex items-center justify-center">
      <p className="text-sm justify-center">Email address does not seem to be registered. Please complete registration on the next screen.</p>
      <div>
        <Image
          src="/loading.svg"
          alt="loading icon"
          width={100}
          height={100}
        />
      </div>
    </div>
  }

  return props.children[0];
}

export function PrivateRoute(Component) {
  return (props) => {
    const router = useRouter()
    const { signedIn } = useAuth()
    // wait to see if the user is logged in or not.
    if (signedIn === null) {
      return <div className="flex items-center justify-center">
        <p className="text-sm">1: Checking authorisation...</p>
        <Image
          src="/loading.svg"
          alt="loading icon"
          width={100}
          height={100}
        />
      </div>
    }

    if (!signedIn) {
      router.push('/login')
      return <div>Redirecting...</div>
    }

    return <PrivateRouteInternal><Component {...props} /></PrivateRouteInternal>;
  }
}

We are still refining the front-end private route file.

elitan commented 2 years ago

Hi @fullakingi, we're focusing all of our efforts at Nhost on Nhost v2 right now, which will come with a new serverless functions-service (instead of the current custom API).

I'd suggest waiting for Nhost v2 to make serverless functions on Nhost. We hope to have Nhost v2 publicly available in a few weeks from now.

fullakingi commented 2 years ago

Ok. I don't have a choice as my client want the feature now as we have too many users.

Ok I look forward to seeing the serverless functions. Excited for Nhost V2. The API is easy for us now. It works fine for us.I just need to know if it will be a breaking change as it is critical for us to be prepared, or will you retain nhost V1 for legacy accounts?

elitan commented 2 years ago

Just to confirm, do you have any issue with the above-mentioned code? Or just wanted to provide the code as a reference for others to learn from?

Nhost v2 will have no effect on the current project being hosted on Nhost. They are separated.