Use Supabase Postgres databaseto keep track of the books I have read starting this year.

In this tutorial I will show you how to create a simple Database using Supabase AI and create a simple visual site to store books you have read.

tl&dr:

The page should look like this: http://juansoultrek.com/books

If you want the code, go to: https://github.com/juansoultrek/supabase-react-books

Ingredients needed :

  • supabase account
  • NPM and npx to create a new project react project.

  1. Create a Supabase account

Go to https://supabase.com/ and create an account.

Then create a new organization

Create a new project

I named mine: SupabaseAIDatabaseForMyReadBooks

Why? Because Supabase has a new AI feature that allows you to automagically create a DB schema for you. Go to the SQl editor:

And let’s write this:

Build a readBooks table with columns for book id, title, author, genre, description, date purchased, date finished reading, physical or digital and personal notes

And magic:

We now possess a simple schema for our readBooks table .

Click on the green Accept changes button and click RUN to execute the query.

I actually want to use enum for the format of the book, and I want to limit if to physical or digital, so let’s modify the table:

-- Create the enum type if not already defined

CREATE TYPE format_options_enum AS ENUM ('physical', 'digital');

-- Alter the table to change the data type of the format column

-- to use the format_options_enum enum type

ALTER TABLE readBooks

ALTER COLUMN format

TYPE format_options_enum

USING format::format_options_enum;

If you run the query and then go to Database – Schema Visualizer, it should look like this:

Start a new react project using:

npx create-react-app <name of your app>
npm install react-bootstrap bootstrap @supabase/supabase.js

Create a a new file called supabaseClient.js and add the following:

import {createClient} from "@supabase/supabase-js";

export const supabase = createClient(
    process.env.REACT_APP_SUPABASE_CLIENT_URL,
    process.env.REACT_APP_SUPABASE_PROJECT_KEY);

Now create an .env file and add the following variables: ( follow the last tutorial to view how to get the credentials from supabas)

REACT_APP_SUPABASE_CLIENT_URL = <your client url?
REACT_APP_SUPABASE_PROJECT_KEY = <your project key?

Lets create a navigation menu called navBar.js:

import React, { useEffect, useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Navbar, Container, Nav } from 'react-bootstrap';
import { Link } from "react-router-dom";
import { useNavigate } from 'react-router-dom';
import { supabase } from './supabaseClient';

function NavBar() {
    const navigate = useNavigate();
    const [user, setUser] = useState({});

    useEffect(() => {
        async function getUserData() {
            const { data, error } = await supabase.auth.getUser();

            if (!error && data?.user) {
                setUser(data.user);
                console.log(data.user.email);
            }
        }
        getUserData();
    }, []);

    const handleLogout = async () => {
        await supabase.auth.signOut();
        window.location.reload();
        navigate("/books/read"); 
    }

    return (
        <Navbar bg="light" expand="lg">
            <Container>
                <Link to="/books/read" style={{ textDecoration: 'none' }}>
                    <Navbar.Brand>
                        My Read Books {user.email ? <span style={{ fontSize: '12px', color: 'blue' }}>{user.email}</span> : null}
                    </Navbar.Brand>
                </Link>

                <Navbar.Toggle aria-controls="basic-navbar-nav" />
                <Navbar.Collapse id="basic-navbar-nav">
                    <Nav className="mx-auto">
                        <Link to="/books/login" className={`nav-link ${user.email ? 'd-none' : ''}`}>
                            Login
                        </Link>
                        {user.email && (
                            <>
                                <Link to="/books/create" className="nav-link">
                                    Add Book
                                </Link>
                                <Link to="/books/edit" className="nav-link">
                                    Edit Book
                                </Link>
                                <Link to="/books/read" className="nav-link">
                                    Read Books
                                </Link>
                                <Link to="#" className="nav-link" onClick={handleLogout}>
                                    Log Out
                                </Link>
                            </>
                        )}
                    </Nav>
                    <Nav className="ml-auto">
                        <Nav.Item>Created by Juan</Nav.Item>
                    </Nav>
                </Navbar.Collapse>
            </Container>
        </Navbar>
    );
}

export default NavBar;

The navigation menu will allow to navigate between creating, reading, and editing/deleting a book.
Only authenticated users will be able to create and edit/delete books.

Let’s now create a login page, create a new file called loginBook.js

import React, { useState } from 'react';
import { supabase } from './supabaseClient';

function LoginForm() {
    const [loading, setLoading] = useState(false);
    const [email, setEmail] = useState('');
    const [message, setMessage] = useState('');
    const [alertVariant, setAlertVariant] = useState('success');

    const handleLogin = async (event) => {
        event.preventDefault();

        setLoading(true);
        const {error } = await supabase.auth.signInWithOtp({
            email: email,
            options: {
                shouldCreateUser: false
            }
        });

        if (error) {
            setMessage("Only valid users.");
            setAlertVariant("danger");
        } else {
            setMessage('Check your email for the login link!');
            setAlertVariant('success');
        }

        setLoading(false);
    }

    return (
        <div className="row justify-content-center">
            <div className="col-6">
                <div className="card">
                    <div className="card-body">
                        <h1 className="card-title">Login</h1>
                        {message && (
                            <div className={`alert alert-${alertVariant}`} role="alert">
                                {message}
                            </div>
                        )}
                        <p className="card-text">Sign in via a magic link with your email below</p>
                        <form onSubmit={handleLogin}>
                            <div className="form-group">
                                <input
                                    type="email"
                                    className="form-control"
                                    placeholder="Your email"
                                    value={email}
                                    required={true}
                                    onChange={(e) => setEmail(e.target.value)}
                                />
                            </div>
                            <div className="form-group">
                                <button className="btn btn-primary" disabled={loading}>
                                    {loading ? 'Loading' : 'Send magic link'}
                                </button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default LoginForm;

Make sure to add authorized users in your Supabase account, so they will be able to login via Magic Links. Just got to Dashboard / Authentication and create an authenticated user.

Now we will create the Router, that will help to navigate and check if we can navigate to certain route.
Add the following, to the existing App.js file

import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom';
import NavBar from './navBar';
import CreateBook from './createBook';
import ReadBook from './readBook';
import EditBook from './editBook';
import LoginBook from './loginBook';
import ProtectedRoute from './protectedRoute';

function App() {
return (
<Router>
<div>
<NavBar />
<Routes>
<Route path="/books/create" element={<ProtectedRoute element={<CreateBook />} />} />
<Route path="/books/read" element={<ReadBook />} />
<Route path="/books/edit" element={<ProtectedRoute element={<EditBook />} />} />
<Route path="/books/login" element={<LoginBook />} />
<Route path="/books/*" element={<Navigate to="/books/read" />} />
</Routes>
</div>
</Router>
);
}

export default App;

Let’s create the protected Route file, that will authorize the use of certain pages. Create a file named protectedRoute.js

import React, { useState, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { supabase } from './supabaseClient';

function ProtectedRoute({ element }) {
const [session, setSession] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
setLoading(false);
});

supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);

if (loading) {
return <p>Loading...</p>;
}

if (session) {
return element;
} else {
return <Navigate to="/login" />;
}
}

export default ProtectedRoute;

Now let’s create the input fileds to add data to the Postgres database. Create a new file called createBooks.js:

import React, { useState } from 'react';
import { Container, Form, Row, Col, Button } from 'react-bootstrap';
import { supabase } from "./supabaseClient";
import { Alert } from 'react-bootstrap';


function CreateBook() {
    const [title, setTitle] = useState("");
    const [author, setAuthor] = useState("");
    const [genre, setGenre] = useState("");
    const [description, setDescription] = useState("");
    const [datePurchased, setDatePurchased] = useState("");
    const [dateFinishedReading, setDateFinishedReading] = useState("");
    const [format, setFormat] = useState("");
    const [personalNotes, setPersonalNotes] = useState("");

    const [insertSuccess, setInsertSuccess] = useState(false);
    const [message, setMessage] = useState('');
    const [alertVariant, setAlertVariant] = useState("success");

    const scrollToTop = () => {
        window.scrollTo(0, 0);
    };

    const resetForm = () => {
        setTitle("");
        setAuthor("");
        setGenre("");
        setDescription("");
        setDatePurchased("");
        setDateFinishedReading("");
        setFormat("");
        setPersonalNotes("");

        console.log('resetForm called')
    };

    const addNewBook = async () => {

        if (
            title.trim() === '' ||
            author.trim() === '' ||
            genre.trim() === '' ||
            description.trim() === '' ||
            datePurchased.trim() === '' ||
            dateFinishedReading.trim() === '' ||
            format === ''
        ) {
            setMessage('Please fill out all required fields.');
            setAlertVariant('danger');
            scrollToTop();
        } else {
            try {
                const {error} = await supabase
                    .from("readbooks")
                    .insert({
                        title,
                        author,
                        genre,
                        description,
                        date_purchased: datePurchased,
                        date_finished_reading: dateFinishedReading,
                        format_options: format,
                        personal_notes: personalNotes
                    })
                    .single();
                if (error) throw error;
                setInsertSuccess(true);
                resetForm();
                setMessage('Book added successfully.');

                setAlertVariant("success");
                scrollToTop();

                setTimeout(() => {
                    setInsertSuccess(false);
                }, 3000);
            } catch (error) {
                console.log("This is the error:" + error.message);
                setMessage('An error occurred while adding the book.');
                setAlertVariant("danger");
                scrollToTop();
                console.error('Error:', error);
            }
        }
    };

    return (
        <Container>
            {insertSuccess && (
                <Alert variant={alertVariant}>
                    {message}
                </Alert>
            )}

            {!insertSuccess && message && (
                <Alert variant={alertVariant}>
                    {message}
                </Alert>
            )}

            <Row>
                <Col xs={12} md={8}>
                    <h3>Add Book For Supabase Database</h3>
                    <Form.Label>Title</Form.Label>
                    <Form.Control
                        type = "text"
                        id="title"
                        value={title}
                        onChange={(e) => setTitle(e.target.value)}
                    />
                    <Form.Label>Author</Form.Label>
                    <Form.Control
                        type = "text"
                        id="author"
                        value={author}
                        onChange={(e) => setAuthor(e.target.value)}
                    />
                    <Form.Label>Genre</Form.Label>
                    <Form.Control
                        type = "text"
                        id="genre"
                        value={genre}
                        onChange={(e) => setGenre(e.target.value)}
                    />
                    <Form.Label>Description</Form.Label>
                    <Form.Control
                        as = "textarea"
                        id="description"
                        value={description}
                        rows={3}
                        onChange={(e) => setDescription(e.target.value)}
                    />
                    <Form.Label>Date Purchased</Form.Label>
                    <Form.Control
                        type = "date"
                        id="datePurchased"
                        value={datePurchased}
                        onChange={(e) => setDatePurchased(e.target.value)}
                    />
                    <Form.Label>Date Finished Reading</Form.Label>
                    <Form.Control
                        type = "date"
                        id="dateFinishedReading"
                        value={dateFinishedReading}
                        onChange={(e) => setDateFinishedReading(e.target.value)}
                    />
                    <Form.Label>Personal Notes</Form.Label>
                    <Form.Control
                        as = "textarea"
                        id="personalNotes"
                        value={personalNotes}
                        rows={3}
                        onChange={(e) => setPersonalNotes(e.target.value)}
                    />
                    <Form.Label>Format</Form.Label>
                    <Form.Control
                        as="select"
                        value={format}
                        onChange={e => setFormat(e.target.value)}
                    >
                        <option value="">Select an option:</option>
                        <option value="digital">Digital</option>
                        <option value="physical">Physical</option>
                    </Form.Control>

                    <br></br>
                    <Button onClick={ () => addNewBook() }>Add Book</Button>
                </Col>
            </Row>

        </Container>

    );
}

export default CreateBook;

We might also want to edit the data , let’s create a file called editBooks.js

import React, { useState, useEffect } from 'react';
import { Container, Form, Row, Col, Button, Table, Alert } from 'react-bootstrap';
import { supabase } from './supabaseClient';

function EditBook() {
    const [books, setBooks] = useState([]);
    const [editMode, setEditMode] = useState(false);
    const [selectedBook, setSelectedBook] = useState({});
    const [updateSuccess, setUpdateSuccess] = useState(false);
    const [alertVariant, setAlertVariant] = useState('success');
    const [message, setMessage] = useState('');

    useEffect(() => {
        const fetchBooks = async () => {

            const { data, error } = await supabase.from('readbooks').select('*');// eslint-disable-line no-unused-vars
            if (error) {
                console.error('Error fetching books:', error);
            } else {
                setBooks(data);
            }
        };

        fetchBooks();
    }, [updateSuccess]); // Update the effect dependency

    const handleEdit = (book) => {
        setEditMode(true);
        // Clone the book object to avoid modifying the original book
        setSelectedBook({ ...book });
    };

    const scrollToTop = () => {
        window.scrollTo(0, 0);
    };

    const handleSave = async () => {
        const {error } = await supabase
            .from('readbooks')
            .update({
                title: selectedBook.title,
                author: selectedBook.author,
                genre: selectedBook.genre,
                description: selectedBook.description,
                date_purchased: selectedBook.date_purchased,
                date_finished_reading: selectedBook.date_finished_reading,
                format_options: selectedBook.format_options,
                personal_notes: selectedBook.personal_notes,
            })
            .eq('id', selectedBook.id);

        if (error) {
            console.error('Error updating book:', error);
            setMessage('An error occurred while updating the book.');
            setAlertVariant('danger');
            scrollToTop();
        } else {
            setEditMode(false);
            setUpdateSuccess(true);
            setMessage('Book updated successfully.');
            setAlertVariant('success');
            scrollToTop();
        }
    };

    return (
        <Container>
            {updateSuccess && (
                <Alert variant={alertVariant}>
                    {message}
                </Alert>
            )}

            {!updateSuccess && message && (
                <Alert variant={alertVariant}>
                    {message}
                </Alert>
            )}

            <Row>
                <Col xs={12} md={8}>
                    <h3>Read Books</h3>
                    {books.length === 0 ? (
                        <p>No books to display.</p>
                    ) : (
                        <Table striped bordered hover>
                            <thead>
                            <tr>
                                <th>Title</th>
                                <th>Author</th>
                                <th>Genre</th>
                                <th>Description</th>
                                <th>Date Purchased</th>
                                <th>Date Finished Reading</th>
                                <th>Format Options</th>
                                <th>Personal Notes</th>
                                <th>Actions</th>
                            </tr>
                            </thead>
                            <tbody>
                            {books.map((book) => (
                                <tr key={book.id}>
                                    <td>
                                        {editMode && selectedBook.id === book.id ? (
                                            <Form.Control
                                                type="text"
                                                value={selectedBook.title}
                                                onChange={(e) => setSelectedBook({ ...selectedBook, title: e.target.value })}
                                            />
                                        ) : (
                                            book.title
                                        )}
                                    </td>
                                    <td>
                                        {editMode && selectedBook.id === book.id ? (
                                            <Form.Control
                                                type="text"
                                                value={selectedBook.author}
                                                onChange={(e) => setSelectedBook({ ...selectedBook, author: e.target.value })}
                                            />
                                        ) : (
                                            book.author
                                        )}
                                    </td>
                                    <td>
                                        {editMode && selectedBook.id === book.id ? (
                                            <Form.Control
                                                type="text"
                                                value={selectedBook.genre}
                                                onChange={(e) => setSelectedBook({ ...selectedBook, genre: e.target.value })}
                                            />
                                        ) : (
                                            book.genre
                                        )}
                                    </td>
                                    <td>
                                        {editMode && selectedBook.id === book.id ? (
                                            <Form.Control
                                                as="textarea"
                                                value={selectedBook.description}
                                                onChange={(e) => setSelectedBook({ ...selectedBook, description: e.target.value })}
                                            />
                                        ) : (
                                            book.description
                                        )}
                                    </td>
                                    <td>
                                        {editMode && selectedBook.id === book.id ? (
                                            <Form.Control
                                                type="date"
                                                value={selectedBook.date_purchased}
                                                onChange={(e) => setSelectedBook({ ...selectedBook, date_purchased: e.target.value })}
                                            />
                                        ) : (
                                            book.date_purchased
                                        )}
                                    </td>
                                    <td>
                                        {editMode && selectedBook.id === book.id ? (
                                            <Form.Control
                                                type="date"
                                                value={selectedBook.date_finished_reading}
                                                onChange={(e) => setSelectedBook({ ...selectedBook, date_finished_reading: e.target.value })}
                                            />
                                        ) : (
                                            book.date_finished_reading
                                        )}
                                    </td>
                                    <td>
                                        {editMode ? (
                                            <Form.Control
                                                as="select"
                                                value={selectedBook.format_options} // Set the value based on the selected book
                                                onChange={(e) =>
                                                    setSelectedBook({
                                                        ...selectedBook,
                                                        format_options: e.target.value,
                                                    })
                                                }
                                            >
                                                <option value="physical">Physical</option>
                                                <option value="digital">Digital</option>
                                            </Form.Control>
                                        ) : (
                                            book.format_options
                                        )}
                                    </td>
                                    <td>
                                        {editMode && selectedBook.id === book.id ? (
                                            <Form.Control
                                                as="textarea"
                                                value={selectedBook.personal_notes}
                                                onChange={(e) => setSelectedBook({ ...selectedBook, personal_notes: e.target.value })}
                                            />
                                        ) : (
                                            book.personal_notes
                                        )}
                                    </td>
                                    <td>
                                        {editMode && selectedBook.id === book.id ? (
                                            <Button variant="success" onClick={handleSave}>
                                                Save
                                            </Button>
                                        ) : (
                                            <Button variant="primary" onClick={() => handleEdit(book)}>
                                                Edit
                                            </Button>
                                        )}
                                    </td>
                                </tr>
                            ))}

                            </tbody>
                        </Table>
                    )}
                </Col>
            </Row>
        </Container>
    );
}

export default EditBook;

And finally the default page, that will show the actual books. Create a file named readBooks.js

import React, { useState, useEffect } from 'react';
import { Container, Row, Col, Table } from 'react-bootstrap';
import { supabase } from './supabaseClient';

function ReadBook() {
    console.log('api_url-->' +process.env.REACT_APP_SUPABASE_CLIENT_URL);

    const [books, setBooks] = useState([]);

    useEffect(() => {
        // Fetch existing books from the database and populate the books state.
        const fetchBooks = async () => {
            const { data, error } = await supabase.from('readbooks').select('*');
            if (error) {
                console.error ('Error fetching books:', error);
            } else {
                setBooks(data);
            }
        };

        fetchBooks();
    }, []);

    return (
        <Container>
            <Row>
                <Col xs={12} md={8}>
                    <h3>Read Books</h3>
                    {books.length === 0 ? (
                        <p>No books to display.</p>
                    ) : (
                        <Table striped bordered hover style={{ width: '100%' }}>
                            <thead>
                            <tr>
                                <th>Title</th>
                                <th>Author</th>
                                <th>Genre</th>
                                <th>Description</th>
                                <th>Date Purchased</th>
                                <th>Date Finished Reading</th>
                                <th>Format Options</th>
                                <th>Personal Notes</th>
                            </tr>
                            </thead>
                            <tbody>
                            {books.map((book) => (
                                <tr key={book.id}>
                                    <td>{book.title}</td>
                                    <td>{book.author}</td>
                                    <td>{book.genre}</td>
                                    <td>{book.description}</td>
                                    <td>{book.date_purchased}</td>
                                    <td>{book.date_finished_reading}</td>
                                    <td>{book.format_options}</td>
                                    <td>{book.personal_notes}</td>
                                </tr>
                            ))}
                            </tbody>
                        </Table>
                    )}
                </Col>
            </Row>
        </Container>
    );
}

export default ReadBook;

The page should look like this: http://juansoultrek.com/books

If you want the code, go to: https://github.com/juansoultrek/supabase-react-books

Login to any platform using Supabase and React

In this tutorial I am going to show you how to use Supabase and React to authenticate to multiple providers.

Ingredients needed :

Create a Supabase account

Go to https://supabase.com/ and create an account.

Then create a new organization

Then go to AuthenticationURL Configuration and setup the following Redirect URL:
http://localhost:3000/success

Let’s start with Discord:

Go to : https://discord.com/developers/applications

And click on New Application:

Make sure to copy and secure the APPLICATION ID and PUBLIC KEY

Now, let’s go back to Supabase and go to:

Then go to AuthenticationProvidersAuth Providers

Enable Discord and add the Client ID and Client Secret

In this same page copy the Supbase redirect URl ( Callback URL) and go back to Discord and paste it.

Setup a new react app in your favorite IDE, type:
npx create-react-app <name-of-your-app>

After it finishes installing, cd into client

cd <name-of-your-app>

And type:

npm install @supabase/auth-ui-react @supabase/supabase-js @supabase/auth-ui-shared

This will install Auth UI, it is a pre-built React component for authenticating users.

And let’s also install to handle the local routes:

npm install react-router-dom

Make sure your App.js file looks like this:

import React from 'react';
import './App.css';
import  { BrowserRouter as Router, Routes, Route} from "react-router-dom";
import Login from "./pages/loginPage";
import Success from "./pages/successPage";


function App() {
  return (
      <Router>
          <Routes>
              <Route path="/" element={<Login/>} />
              <Route path="/success" element={<Success/>} />
          </Routes>
      </Router>
  );
}

export default App;

You can create a folder to store the login and success page.
On your src directory create a folder named pages and create a loginPage.js and a successPage.js

Create a login and success function like this one:

function Success() {
    return (
        <div className="App">
            <header className="App-header">
                <h1>Success</h1>
            </header>
        </div>
    );
}

export default Success;

If you visit http://localhost:3000/success

You should see the success page:

Now let’s install this package: npm i –save-dev dotenv
So we can save our variable in an .env file

Create the file, and add the following variables:
REACT_APP_SUPABASE_CLIENT_URL =

REACT_APP_SUPABASE_PROJECT_KEY

In the loginPage.js add the supabase client credentials, the file should look like this:

import {createClient} from "@supabase/supabase-js";
import {useNavigate} from "react-router-dom";
import {Auth} from "@supabase/auth-ui-react";
import {ThemeSupa,} from '@supabase/auth-ui-shared'

const supabase = createClient(
    process.env.REACT_APP_SUPABASE_CLIENT_URL,
    process.env.REACT_APP_SUPABASE_PROJECT_KEY
)
function Login() {
    const navigate = useNavigate();
    supabase.auth.onAuthStateChange(async (event) =>{
        if (event === "SIGNED_IN") {
            navigate("/success");
        } else {
            navigate("/");
        }
    })
    return (
        <div className="App">
            <header className="App-header">
                <Auth
                    supabaseClient={supabase}
                    appearance={{ theme: ThemeSupa }}
                    providers={['discord']}
                />
            </header>
        </div>
    );
}

export default Login;

The login should look like this:

If you go ahead and login to Discord, you will be redirected to the Success page.

Now lets add a final piece of code so we can sign out the user.

import {createClient} from "@supabase/supabase-js";
import {useNavigate} from "react-router-dom";
import React, { useEffect, useState} from "react";


const supabase = createClient(
    process.env.REACT_APP_SUPABASE_CLIENT_URL,
    process.env.REACT_APP_SUPABASE_PROJECT_KEY
)
function Success() {

    const [user, setUser] = useState({});
    const navigate = useNavigate();

    useEffect( () => {
        async function getUserData(){
            await supabase.auth.getUser().then((value) => {
                if(value.data?.user){
                    console.log(value.data.user)
                    setUser(value.data.user);
                }

            })
        }
        getUserData();
    }, []);

    async function signOutUser() {
        const {error} = await supabase.auth.signOut();
        navigate("/");
    }



    return (
        <div className="App">
            <header className="App-header">
                <h1>Success</h1>
                <button onClick={ () =>signOutUser()}>Sign Out
                </button>
            </header>
        </div>
    );
}

export default Success;

This will create a Sign Out button:

RingCentral SMS and Bitcoin Vault

tl;dr : This is not a post for mining bitcoin using the RingCentral SMS app, I intend to use their SMS API to get a notification when a certain cryptocurrency changes price.

I decided to start learning about new cryptocurrencies and invest in digital markets. There is a particular new crypto called Bitcoin Vault that caught my attention.

It is a new and completely decentralized cryptocurrency that is part of the Decentralized Digital Mining Standard with new anti-theft solutions, already available in 3 exchanges. I started mining it at mininigcity.com, if you want to learn more please contact me or use my referral code: https://me.miningcity.com/referral-register/datagum

I want to keep a constant eye on its current price, so I want to be informed when the price changes. I decided to create a script that will send me a SMS using Ring Central’s API whenever the current price varies more than $10 usd (up or down) comparing it with the last price checked.

Ingredients needed :

Steps:

1.- Create a CoinMarketCap API

I use coinmarketcap to check the price of a cryptocurrency, so head to https://coinmarketcap.com/api/ and create a new developer free account.

2.- PHP Script

For this test, I created a folder named RingCentral, and added a php file named cryptoCurrency.php

Open your empty file and copy these lines of code:

<?php
$url = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/map';
$parameters = [
    'symbol' => 'BTC,BTCV'
];

$headers = [
    'Accepts: application/json',
    'X-CMC_PRO_API_KEY: b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c'
];
$qs = http_build_query($parameters); // query string encode the parameters
$request = "{$url}?{$qs}"; // create the request URL


$curl = curl_init(); // Get cURL resource
// Set cURL options
curl_setopt_array($curl, array(
    CURLOPT_URL => $request,            // set the request URL
    CURLOPT_HTTPHEADER => $headers,     // set the headers
    CURLOPT_RETURNTRANSFER => 1         // ask for raw response instead of bool
));

$response = curl_exec($curl); // Send the request, save the response
print_r(json_decode($response)); // print json decoded response
curl_close($curl); // Close request
?>

In this example we are using the /map endpoint.

Make sure to use your own API KEY or it won’t work, run the script, and you will fetch all data related to the symbols sent in the payload parameters: 'symbol' => 'BTC,BTCV'
In this example we can see data for the Bitcoin and Bitcoin Vault currency. This is useful because the API recommends using the internal id instead of the symbol or name.

Now that we have the id (5175) for Bitcoin Vault, let’s use the endpoint that will fetch the current price:  /v1/cryptocurrency/quotes/latest

Copy the following script:

<?php
$url = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest';
$parameters = [
    'id' => '5175'
];

$headers = [
    'Accepts: application/json',
    'X-CMC_PRO_API_KEY: b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c'
];
$qs = http_build_query($parameters); // query string encode the parameters
$request = "{$url}?{$qs}"; // create the request URL


$curl = curl_init(); // Get cURL resource
// Set cURL options
curl_setopt_array($curl, array(
    CURLOPT_URL => $request,            // set the request URL
    CURLOPT_HTTPHEADER => $headers,     // set the headers
    CURLOPT_RETURNTRANSFER => 1         // ask for raw response instead of bool
));

$response = curl_exec($curl); // Send the request, save the response
$result   = json_decode($response,true); //  json decoded response
print_r($result['data']['5175']['quote']['USD']);
curl_close($curl); // Close request

This will print the only part of the response that I am interested, the price:

In order to compare the price we need to at least have a starting point to compare, so we have to create a file where we are going to save the current price.

Create a file called currentBitcoinVaultValue.txt and add the value of 10
What we are going to do is add to our script a code that will read the current value in the file, and then update it with the current bitcoin vault price read from the API.

Copy the following script that will read the current price of BitcoinVault and save it to the .txt file:

<?php
$url = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest';
$parameters = [
    'id' => '5175'
];

$headers = [
    'Accepts: application/json',
    'X-CMC_PRO_API_KEY: b5f708a1-3c63-4152-9e37-3a4a883e3820'
];
$qs = http_build_query($parameters); // query string encode the parameters
$request = "{$url}?{$qs}"; // create the request URL


$curl = curl_init(); // Get cURL resource
// Set cURL options
curl_setopt_array($curl, array(
    CURLOPT_URL => $request,            // set the request URL
    CURLOPT_HTTPHEADER => $headers,     // set the headers
    CURLOPT_RETURNTRANSFER => 1         // ask for raw response instead of bool
));

$response = curl_exec($curl); // Send the request, save the response
$result   = json_decode($response,true); //  json decoded response

$currentBitcoinVaultPrice = $result['data']['5175']['quote']['USD']['price'];

curl_close($curl); // Close request*/

$fileName = 'currentBitcoinVaultValue.txt';
$processFile = fopen($fileName, "r") or die("Unable to open file!");
$oldPrice = fgets($processFile);
file_put_contents($fileName, $currentBitcoinVaultPrice);

My current .txt file now has been updated with the current BitcoinVault price

3.- Add Ring Central’s SMS API to our script

Now let’s continue with the fun part, get an alert when the price has changed. For this tutorial, I want to know when the prices has gone up 10 usd up or 10 usd down. So let’s add the Ring Central SMS API first.

Again I won’t go into details on how to setup an account with Ring Central, you can follow step by step on this post, check step 5(Setup RingCentral SMS API) it is pretty easy and quick to get a developer account.

Let’s add RingCentral’s API, your script should look like these:

<?php
require('vendor/autoload.php');
// CoinMarketCap API
$url         = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest';
$parameters = [
    'id' => '5175'
];

$headers = [
    'Accepts: application/json',
    'X-CMC_PRO_API_KEY: b5f708a1-3c63-4152-9e37-3a4a883e3820'
];

$qs      = http_build_query($parameters); // query string encode the parameters
$request = "{$url}?{$qs}"; // create the request URL

$curl = curl_init(); // Get cURL resource
// Set cURL options
curl_setopt_array($curl, array(
    CURLOPT_URL => $request,            // set the request URL
    CURLOPT_HTTPHEADER => $headers,     // set the headers
    CURLOPT_RETURNTRANSFER => 1         // ask for raw response instead of bool
));

$response = curl_exec($curl); // Send the request, save the response
$result   = json_decode($response,true); //  json decoded response

$currentBitcoinVaultPrice = $result['data']['5175']['quote']['USD']['price'];

curl_close($curl); // Close request*/

$fileName    = 'currentBitcoinVaultValue.txt';
$processFile = fopen($fileName, "r") or die("Unable to open file!");
$oldPrice    = fgets($processFile);
file_put_contents($fileName, $currentBitcoinVaultPrice);

$calc = (float)$currentBitcoinVaultPrice - (float)$oldPrice;

if ($calc >= 10)
{
    smsRingCentralAlert('Bitcoin Vault has gone up to '.$currentBitcoinVaultPrice);
}
if($calc <= -10)
{
    smsRingCentralAlert('Bitcoin Vault has gone down to '.$currentBitcoinVaultPrice);
}

// Call Ring Central SMS API
function smsRingCentralAlert($message)
{
    $RECIPIENT = 'YOUR-TEST-PHONE-NUMBER';
$RINGCENTRAL_CLIENTID = 'YOUR-CLIENT-ID';
$RINGCENTRAL_CLIENTSECRET = 'YOUR-SECRET';
$RINGCENTRAL_SERVER = 'https://platform.devtest.ringcentral.com';
$RINGCENTRAL_USERNAME = 'YOUR-USERNAME';
$RINGCENTRAL_PASSWORD = 'YOUR-PASSWORD';
$RINGCENTRAL_EXTENSION = 'YOUR-EXTENSION';

    $rcsdk = new RingCentral\SDK\SDK($RINGCENTRAL_CLIENTID, $RINGCENTRAL_CLIENTSECRET, $RINGCENTRAL_SERVER);
    $platform = $rcsdk->platform();
    $platform->login($RINGCENTRAL_USERNAME, $RINGCENTRAL_EXTENSION, $RINGCENTRAL_PASSWORD);

    $platform->post('/account/~/extension/~/sms',
        [
            'from' => ['phoneNumber' => $RINGCENTRAL_USERNAME],
            'to' => [['phoneNumber' => $RECIPIENT]],
            'text' => $message
        ]
    );
}

Remember to you use your own API keys.
I made a couple examples using a lower and an greater price to test the script, here are my test results:

4.- Final step Cron Job

In the last tutorial I explained what a cron job is, and how to set it up, you can take a look at all the details here:

For this example I will create a cron job that will trigger our script every hour.

To create a cron job just open your CLI and type:

crontab -e

Make sure to check your current path to your PHP exe. This command will help:

whereis php

The php path is usually :
/usr/bin/php

After you run the crontab, most likely an editor will open, this is an example of the cron I used for this example that will run every hour:
0 */1 * * *. /usr/bin/php /Users/juan/DEV/RingCentral/cryptoCurrency.php >/tmp/stdout.log 2>/tmp/stderr.log

You can change the timing for the cron job, and also change the id for the currency that you want to check the price for.

RingCentral SMS app Round II

Last time I wrote, I mentioned that I wanted to learn how to receive an SMS using Ring Central’s API and I did an example using RingCentral + ngrok + Formstack, you can check it out here.

I also mentioned that I wanted to develop something I could use in my own home, so this time I decided to use SMS Ring Central’s API to notify me when a light has turned on in my house.
And why the hell would anyone mind if your lights are on?
Well I could enumerate a few examples, but you can use your imagination for that.

I have a few Philips HUE lights in my home, most of them at my game-room / office and a few white lights at bedrooms.

Ingredients needed :

STEPS:

1.- HUE Philips API

You need to discover the IP address of the HUE Philips bridge network.

The easiest way is to try this address: https://discovery.meethue.com/, if you can’t find it there, you can also try this detailed steps from the developers at HUE, or like I did, just check the IP at your router. I have three Google routers inside my home, and they work flawlessly, I just opened the Google Wifi app and checked for all connected devices and looked for the Philips hue IP:

Curious about Google’s wifi routers?
You can check them out here

Now that we have the IP, open up a browser and type your IP address followed by /debug/clip.html
This endpoint is HUE’S bridge API debugger:

Let’s generate a username for our device:

  • In the URL field type : /api
  • In the Message Body field type: {“devicetype”:”my_hue_app#Juan“}
  • Hit the POST button.
What is wrong?

Noticed the error message?
“link button not pressed”
We are missing something important here:

The problem is, that we have to physically authenticate with the device before attempting to create a username.It is a security step to prove that we have access to control the lights. So head over to your hue bridge and press the link button.

Now press that POST again and voila.

We have now created our username.

Now you have a username with permissions that you can use to interact with all your lights.

The next endpoint:

https://BRIDGEIPADRESS/api/USERNAME/lights/ returns all the current info on our lights, for this tutorial I am only interested in the light state property named on which is a boolean value. ( true or false).

Here is an example of the API response:

You can use POSTMAN to test call the API.

2.- PHP Script

Let’s have more fun now with some coding. We have to create a simple script that will GET the data that we need from our lights. For this example I will get the name of the light and from the state I want to know the on value (true,false).

<?php
$curl = curl_init();

$internalIp = 'YOUR-IP';
$user       = 'YOUR-USERNAME';
$url        = $internalIp.'/api/'.$user.'/lights';

curl_setopt_array($curl,
    [
    CURLOPT_URL            => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING       => "",
    CURLOPT_MAXREDIRS      => 10,
    CURLOPT_TIMEOUT        => 0,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST  => "GET",
    ]
);

$response = curl_exec($curl);
$result   = json_decode($response, true);
curl_close($curl);

$lights = [];
foreach($result as $key=> $light) {

    $lights[$key]['state'] = $light['state']['on'];
    $lights[$key]['name']  = $light['name'];
}

var_dump($lights);

In this small script we will save in the $lights array key, value information of our light’s name and state->on.

Here you can see an example of some of my lights:

3.- PHP Script using Ring Central’s SMS API

I will use RingCentral’s API to notify if a light state->on property changes to true.

I won’t go into details on how to setup an account with Ring Central, you can follow step by step on this post, check step 5(Setup RingCentral SMS API) it is pretty easy and quick to get a developer account.

We already have the first part that calls HUE’S API:

<?php
require('vendor/autoload.php');

$curl = curl_init();

// Hue Lights credentials
$internalIp = 'YOUR-IP';
$user       = 'YOUR-USERNAME';
$url        = $internalIp.'/api/'.$user.'/lights';

// Call Hue Philips API
curl_setopt_array($curl,
    [
    CURLOPT_URL            => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING       => "",
    CURLOPT_MAXREDIRS      => 10,
    CURLOPT_TIMEOUT        => 0,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST  => "GET",
    ]
);

$response = curl_exec($curl);
$result   = json_decode($response, true);
curl_close($curl);

foreach($result as $key=> $light) {
    if ($light['state']['on'] === true){
        smsRingCentralAlert($light['name']);
    }
}

Now we just add RingCentral’s API

// Call Ring Central SMS API
function smsRingCentralAlert($light)
{

    $message = $light. ' light has been turned on';

$RECIPIENT                = 'YOUR-TEST-PHONE-NUMBER';
$RINGCENTRAL_CLIENTID     = 'YOUR-CLIENT-ID';
$RINGCENTRAL_CLIENTSECRET = 'YOUR-SECRET';
$RINGCENTRAL_SERVER       = 'https://platform.devtest.ringcentral.com';
$RINGCENTRAL_USERNAME     = 'YOUR-USERNAME';
$RINGCENTRAL_PASSWORD     = 'YOUR-PASSWORD';
$RINGCENTRAL_EXTENSION    = 'YOUR-EXTENSION';

$rcsdk    = new RingCentral\SDK\SDK($RINGCENTRAL_CLIENTID, $RINGCENTRAL_CLIENTSECRET, $RINGCENTRAL_SERVER);
$platform = $rcsdk->platform();
$platform->login($RINGCENTRAL_USERNAME, $RINGCENTRAL_EXTENSION, $RINGCENTRAL_PASSWORD);

$platform->post('/account/~/extension/~/sms',
    [
        'from' => [ 'phoneNumber' => $RINGCENTRAL_USERNAME],
        'to'   => [['phoneNumber' => $RECIPIENT]],
        'text' => $message
    ]
);
}

Remember to use your credentials!

If everything goes well, you should receive a sms for each of your lights that is on.

4.- Bonus step – Cron Job

In order to keep checking if the lights are on, we would have to run our PHP script again, and again and again, this is an endless task todo manually, if we use a simple cron job that checks the light status for us that would be a lot easier.

  • To create a cron job we need a few ingredients:
  • Timing – Set minutes, hours, days, months
  • Execute – The cron job needs to the PHP exe to run.
  • Path to script – Full path of where your script is located.
  • Output – (optional) you can save the output of your script or log errors.

To create a cron job just open your CLI and type:

crontab -e

Make sure to check your current path to your PHP exe. This command will help:

whereis php

The php path is usually :
/usr/bin/php

After you run the crontab, most likely an editor will open, this is an example of the cron I used for this post:
*/1 * * * * /usr/bin/php /Users/juan/DEV/RingCentral/smsLights.php >/tmp/stdout.log 2>/tmp/stderr.log

The first part sets the script to run every minute: */1 * * * *
Next is the route to my php exe file /usr/bin/php
Now we need the full path to our script /Users/juan/DEV/RingCentral/smsLights.php
And finally I decided to add a log:
>/tmp/stdout.log 2>/tmp/stderr.log

This is just a simple script that tries to explain how to use all this tools together, after you test remember to comment your cron job, or change the timing, or else you will be spammed every minute haha.

RingCentral SMS app

I wanted to learn how to receive an SMS using Ring Central’s API and write about my experience, I thought of how a simple idea of communication between integrations can benefit any person in numerous ways.

I had many ideas on my mind and I will probably be using them in the future, for example: Receive an SMS when a security camera in my house is activated by motion or sound by my curious golden retriever Floki, or a SMS when a door sensor is activated, when a light turns on inside the house, or maybe even get a SMS when someone pushes code to a repository.

Floki don’t be sad, I will be back in a few hours.

For the SMS API example I wanted to test something simple and that could be very productive, that is why I chose to integrate SMS Ring Central’s API with Formstack.

Formstack is a workplace productivity solution built to transform the way people collect information and put it to work, so what If I could use a Formstack form to collect data and depending on the submit response I got from a user, I could trigger an SMS using Ring Central’s API?

Ingredients needed :

STEPS:

1 Create a simple form in Formstack.

For this example I only used a Name and a Rating field.

Need help creating a form…
https://help.formstack.com/hc/en-us/articles/360019520271-Creating-a-New-Form

2 Install and run temporarily ngrok

Download the package and unzip it, then run:
./ngrok http 8080 (or any other port you would like to use)

It should look something like this:

Need more help using Ngrok:
https://ngrok.com/docs

We are now ready to tunnel:

3 Back in Formstack:

Go to Settings -> Emails & Actions and let’s supercharge this form by adding a Webhook, copy the ngrok forwarding IP to the Webhook URL, select Post with sub-field names and Post with API-friendly keys.

Need more help on webhooks:
https://help.formstack.com/hc/en-us/articles/360019516191-WebHook-Submit-Actions

4 Run a temp PHP web server

For this test, I created a folder named RingCentral, and added a php file named index.php

Inside that folder I ran the following command to start my php web server:

php -S localhost:8080

Sample content of the file:

<?php
echo 'hello Blog API Test';

We can make a quick test, to make sure we communicate with our localhost when submitting a form, so go ahead and submit your test form.

You can check the request data sent in http://127.0.0.1:4040/inspect/http

As you can see, I got my 5 star response value from the Rating field did_you_like_this_blog_tutorial and the name field with value Test1, and we can also see that my local web server responded with hello Blog API Test.

5 Setup RingCentral SMS API

And now for the most interesting part, we can now use the data submitted by the Formstack form that was sent to the localhost through ngrok and send a SMS with RIngCentral’s API.

First we need a developer account for Ring Central’s API, you should be able to create an account here: https://developers.ringcentral.com/

Once you have created an account simply create an SMP APP with one click:

Don’t have the link?
Don’t worry here it is:
https://developer.ringcentral.com/new-app?name=SMS+Quick+Start+App&desc=A+simple+app+to+demo+sending+an+SMS+on+RingCentral&public=false&type=ServerOther&carriers=7710,7310,3420&permissions=SMS,ReadMessages&redirectUri=

Now that your sandbox app is created we need to download the PHP SDK to get started: (Make sure to download to the same directory where you started your php web server and where the file index.php is located.

$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require ringcentral/ringcentral-php

Now simply edit the index.php file we created earlier and enter this code:

<?php
require('vendor/autoload.php');

$RECIPIENT = '<ENTER PHONE NUMBER>';

$RINGCENTRAL_CLIENTID = '<ENTER CLIENT ID>';
$RINGCENTRAL_CLIENTSECRET = '<ENTER CLIENT SECRET>';
$RINGCENTRAL_SERVER = 'https://platform.devtest.ringcentral.com';

$RINGCENTRAL_USERNAME = '<YOUR ACCOUNT PHONE NUMBER>';
$RINGCENTRAL_PASSWORD = '<YOUR ACCOUNT PASSWORD>';
$RINGCENTRAL_EXTENSION = '<YOUR EXTENSION, PROBABLY "101">';

$rcsdk = new RingCentral\SDK\SDK($RINGCENTRAL_CLIENTID, $RINGCENTRAL_CLIENTSECRET, $RINGCENTRAL_SERVER);

$platform = $rcsdk->platform();
$platform->login($RINGCENTRAL_USERNAME, $RINGCENTRAL_EXTENSION, $RINGCENTRAL_PASSWORD);

$resp = $platform->post('/account/~/extension/~/sms',
    array(
       'from' => array ('phoneNumber' => $RINGCENTRAL_USERNAME),
       'to' => array(array('phoneNumber' => $RECIPIENT)),
       'text' => 'Hello World from PHP'
     ));
print_r ("SMS sent. Message status: " . $resp->json()->messageStatus);
?>

Submit another form, check the request at: http://127.0.0.1:4040/inspect/http

And you should see a response like this one:

And immediately receive an sms that says:

Now to integrate everything together, just add this to the code before your credentials:

$find      = ['first =','last ='];
$name      = str_replace($find,'',$_REQUEST['name']);
$stars     = $_REQUEST['did_you_like_this_blog_tutorial'];
$message   = 'You received '. $stars . ' stars from'. $name;

And replace the $resp array text value to: $message

$resp = $platform->post('/account/~/extension/~/sms',
    array(
        'from' => array ('phoneNumber' => $RINGCENTRAL_USERNAME),
        'to'   => array(array('phoneNumber' => $RECIPIENT)),
        'text' => $message
    ));
Looks like someone rated me with 5 stars!

And that is it for now. I hope you enjoyed learning how to integrate Formstack, ngrok and RingCentral’s SMS API, because I sured did.

BE A GAME CHANGER

BE A GAME CHANGER

I learned about RingCentral while cruising and assisting to a web tech conference last summer called CoderCruise. This was the first time ever for me on a Cruise, so I decided to invite my wife and we also took our newborn with us. We had an amazing time cruising to The Bahamas, the cruise director named NoNo kept us engaged all the time when she announced cruise activities, and that was so funny.

If you want to learn more about Ring Central’s Game Changers please visit: http://gamechanging.dev

RingCentral game changers is an awesome way to engage future users, developers, and customers to learn more about RingCentral products, experiment with their API and grow skills while completing fun activities, such as uploading a video, or sharing a meme and also learning activities like reading or even writing a blog post, watching videos, use their API, participate with the Developers community, ask questions, and the best of all is that you have the opportunity to win fabulous prizes.

I learned new things in the different sessions during codercruise, one of my favorite ones where one from Paco Vu a Sr. Developer Evangelist from RingCentral , he talked about Harnessing the Power of Unstructured Data with Applied Machine Learning, it was very interesting and kept me intrigued wanting to learn more about their company and API. Another one I enjoyed was the talk about Artificial Intelligence with Norah Klintberg, she gave a lecture and guide to our own AI application in three steps. She even made a nice demo that showed her work. Joel’s Lord talk about history of cryptography named “From Caesar Cipher to Quantum Cryptography”, took in a history journey on how humans of all times have used ciphers. And finally a talk about gaming, I consider myself an avid gamer, so the talk called “A Modern Architectural Review of text-based adventure games” given by Kevin Griffin was incredibly , we had a discussion on how we can use modern approaches to solve some complex problems that would not have scaled on early computers.

This was different from any conference I have experienced I can say it is unique and exciting to have a conference in a cruise and I can’t wait for 2020.

Mateo and I cruising The Bahamas during CoderCruise 2019

My advice to anyone attending next year for the first time is to be ready to have a great time.You can check them out here: https://www.codercruise.com/.

I highly recommend the CoderCruise conference If you want a new awesome experience while learning on different topics and spend a good and fun time and make some new friends.