https://d226lax1qjow5r.cloudfront.net/blog/blogposts/talk-to-your-database-with-directus/database_directus.png

Hable con su base de datos con Directus

Publicado el March 5, 2024

Tiempo de lectura: 11 minutos

Hable con su base de datos con Directus

"Jugar con tus puntos fuertes" es una parte importante del desarrollo de software. Muchos problemas y errores se producen cuando alguien construye algo que no es de su especialidad. La autenticación y las bases de datos son dos de esas cosas que pueden ser increíblemente difíciles de entender, y si estás construyendo una aplicación, ¿por qué deberías gastar un montón de tiempo en cosas que no están directamente relacionadas con tu aplicación?

Directus nos permite descargar la gestión de la base de datos a un sistema externo y facilitar el trabajo con nuestros datos. No sólo se puede utilizar para almacenar datos, sino que se puede utilizar para exponer nuestros datos de múltiples maneras distintas a nuestra aplicación. Puedes consultar su 100+ More Things To Build para tener una idea de todo lo que hacen.

Echemos un vistazo a su oferta de bases de datos. Recorreremos una aplicación de muestra que permite a los usuarios comprar y cancelar entradas de cine, y utilizaremos Directus para manejar el almacenamiento de datos. A continuación, podemos utilizar la Voice API de Vonage para permitir a los usuarios llamar, gestionar sus entradas y dejar que Directus u otros servicios se encarguen de mostrar las cosas en la web.

Requisitos previos

  1. Una cuenta de Vonage Developer. Haz clic en Registrarse para crear una si aún no la tienes.

  2. A Directus Account.

  3. Un sistema de túneles como ngrok.

Configuración de Directus

Si aún no lo ha hecho, diríjase a la Guía de inicio rápido de Directus y siga sus instrucciones para configurar una cuenta de Directus Cloud (que estamos utilizando para esta demostración), o para configurar Directus localmente. Usted necesitará una cuenta de GitHub para inscribirse en una cuenta en la nube.

Necesitamos configurar las fuentes de datos a las que accederemos desde Directus. Configuraremos una colección "Películas", que es una lista de las películas que se están proyectando actualmente. También tendremos una colección "Showtimes" que contiene todos los horarios individuales que puede tener una película. También tendremos una colección de "Entradas", que son las entradas individuales que se han comprado.

Las películas estarán relacionadas con los horarios, y las entradas con los horarios.

Para crear una nueva colección en Directus, haga clic en el icono de engranaje de la barra lateral y seleccione "Modelo de datos".

Directus Sidebar - SettingsDirectus Sidebar - Settings

Esto le dará una lista de los modelos que tiene actualmente. Haz clic en el botón grande "+" en la esquina superior derecha de la pantalla para crear un nuevo modelo.

Creating a new collectionCreating a new collection in Directus

Dale un nombre al modelo, en este caso, "películas". Este será el nombre con el que haremos referencia a la colección cuando la llamemos a través del SDK de Directus. Haga clic en el botón de flecha derecha para continuar.

Puede añadir campos adicionales, pero por ahora los dejaremos sin seleccionar. Haga clic en el icono de marca de verificación de la esquina superior derecha para generar el modelo.

Esto le llevará a la pantalla de edición del modelo.

Movie Collection Edit ScreenMovie Collection Edit Screen

Aquí podemos crear nuevos campos y añadirlos al modelo. La colección "Películas" es bastante simple, así que haz clic en "Crear campo" y añadiremos los siguientes campos:

  • título - Seleccione "Entrada", el tipo es "Cadena" y seleccione "Obligatorio".

  • descripción - Seleccione "Datetime", el tipo es "Datetime", y seleccione "Required"

  • hora_inicio - Seleccione "Entrada", el tipo es "Cadena" y seleccione "Obligatorio".

También necesitaremos crear dos modelos más y sus campos asociados:

  • horarios

    • movie_id - Seleccione "Many to One" en Relational, y en "Related Collection" introduzca "movies"

    • hora_inicio - Seleccione "Datetime", el tipo es "Datetime" y seleccione "Required".

  • entradas

    • showtime_id - Seleccione "Many to One" en Relational, y en "Related Collection" introduzca "showtimes"

    • usuario - Seleccione "Muchos a uno" en Relacional, y en "Colección relacionada" introduzca "directus_users"

Una vez creadas esas colecciones, puede introducir algunas películas en la colección Películas y algunos horarios de proyección asociados.

Para nuestra demo, también querremos crear un usuario que interactuará con nuestras colecciones en nombre del usuario. Hacemos esto porque alguien que llame no será capaz de introducir un nombre de usuario y una contraseña, así que aprovecharemos la función setToken() composable en el cliente Directus.

Haga clic en el icono Usuarios de la barra lateral para ir a la página Usuarios. Haz clic en el icono "+" de la esquina superior derecha para crear un nuevo usuario. Ten cuidado de no hacer clic en el botón "Invitar" que aparece justo al lado.

Rellene el formulario de usuario y, cerca de la parte inferior, en "Opciones de administración", asigne al usuario el rol de "Usuario de la aplicación" y, a continuación, haga clic en "+" dentro del cuadro "Token". No pierda de vista este token, ya que lo necesitaremos para configurar nuestra aplicación.

Haga clic en el botón Marca de verificación situado en la esquina superior derecha para guardar el usuario.

Inicio de ngrok

Necesitaremos alguna forma de hacer que los servidores de Vonage contacten con nuestra aplicación, y la forma más sencilla de hacerlo es utilizando un sistema de túneles como ngrok. Dirígete a ngrokregístrate para obtener una Account y sigue su guía de inicio rápido para instalarlo en tu PC.

Una vez instalado, podemos iniciarlo con:

ngrok http 3000

Esto abrirá un túnel al mundo exterior a través de los servidores de ngrok, incluyendo una URL que tenga SSL activado. Cuando se ejecuta ngrok le dará una salida de estado poco agradable para que pueda comprobar los eventos que fluyen a través del sistema. Necesitaremos tomar la dirección "Forwarding" en el lado izquierdo del archivo ->que es nuestra URL pública. Necesitaremos esta URL en un momento.

Configuración de Vonage

Utilizaremos la Voice API de Vonage para permitir que un usuario llame a nuestra "Movie Phone System", por lo que necesitaremos configurar una aplicación de Vonage. Las Vonage Applications son esencialmente grupos de configuraciones que interactuarán con aplicaciones. Para la mayoría de nuestros servicios, serán "devoluciones de llamada" o URL que usaremos para enviar información a tu aplicación. En el caso de Voice, estas devoluciones de llamada se usan para hacer cosas como informarte sobre un evento de llamada o una URL para manejar el inicio de una llamada.

Acceda a su Panel del desarrolladory diríjase a "Applications"Vamos a crear una nueva aplicación, así que haga clic en "+ Crear una nueva aplicación."

Démosle a nuestra aplicación un nombre como "Directus IVR" para que podamos encontrarla más tarde. Como estamos usando una aplicación de Vonage, necesitamos generar una clave privada para firmar nuestro JWT. Para obtener más información sobre cómo funciona la autenticación JWT de Vonage, consulta nuestra documentación sobre autenticación. Guarda este archivo de clave privada ya que lo necesitaremos aquí dentro de un rato.

Desplázate un poco hacia abajo y activa el botón junto a "Voice" para abrir la configuración de voz. Usando la URL ngrok, que copiamos hace un momento,

  • URL de respuesta - Establecer en "HTTP POST", y el valor a <ngrok URL>/

  • URL del evento - Establecer en "HTTP POST", y el valor a <ngrok URL>/events

  • URL de retorno - Deje en "HTTP GET" y el valor en blanco

A continuación, desplácese hacia abajo y haga clic en "Generar nueva solicitud".

Una vez creada la aplicación, aparecerá la página de información de la nueva aplicación. En la parte superior hay un ID de aplicación que necesitaremos para configurar nuestro código, así que cópialo. También necesitaremos un número de teléfono, así que en la sección "Linked Numbers" (Números vinculados), vincula uno de tus números haciendo clic en el botón "Linked" (Vinculado) de un número que poseas. Si aún no tienes un número, dirígete a la sección "Comprar Numbers"Ten en cuenta que algunos países pueden tener restricciones a la hora de comprar números.

Nuestro Código

Si quieres seguirnos, puedes coger el código.

Configurar nuestra aplicación

Necesitamos introducir en nuestra aplicación información específica sobre usted. Copie el archivo .env.dist a un nuevo archivo llamado .envy ábrelo para editarlo. Hay cuatro variables que necesitamos asignar en este archivo que nuestra aplicación necesitará:

  1. INSTANCIA_DIRECTUS - Esta es la URL para su instancia de Directus. Será algo parecido a "https://my-instance.directus.app/"

  2. DIRECTUS_TOKEN - El token estático que generamos anteriormente para nuestro usuario Directus

  3. API_APPLICATION_ID - El ID de aplicación de Vonage que generamos

  4. CLAVE_PRIVADA - Una versión codificada en base64 de la clave privada que descargamos para nuestra aplicación. Echa un vistazo a nuestro artículo sobre claves privadas en variables de entorno para saber cómo convertir el archivo private.key en una cadena base64

Guarda este archivo.

El cliente Directus

Directus ofrece un SDK JavaScript que utilizaremos para acceder a nuestra instancia de base de datos Directus. La información completa se puede encontrar en la documentación Directus JavaScript SDKpero vamos a crear una instancia del cliente. Dado que vamos a utilizar un usuario del sistema para acceder a nuestra base de datos Directus podemos eludir la cuestión de la autenticación por usuario, por lo que creamos un cliente Directus reutilizable para utilizar en toda la aplicación.

# src/Directus.js

export function getDirectusClient() {
    return createDirectus(process.env.DIRECTUS_INSTANCE)
        .with(staticToken(process.env.DIRECTUS_TOKEN))
        .with(rest());
}

El SDK de Directus es componible, lo que significa que empiezas con un cliente que necesita ser configurado para su uso. En nuestro caso, vamos a utilizar un staticToken() para la autenticación y utilizar el método rest() para configurar el acceso a través de la API REST en lugar de la API GraphQL.

A lo largo de nuestra aplicación, llamaremos a este método getDirectusClient() para utilizar este cliente compuesto en nuestro código.

Dado que estamos utilizando la API REST, nuestro cliente expondrá un método request() que nos permita interactuar con su API. Esto se hace a través de una combinación de métodos que pasamos al método request() como readItems() o deleteItem().

const directus = getDirectusClient();
const tickets = await directus.request(readItems(
    // Collection we are accessing
    'tickets',
    // Options for what we return and search for
    {
        'fields': [
            'id',
            'showtime_id.*.*'
        ],
        'sort': [
            'showtime_id.start_time'
        ],
        'filter': {
            'user': {
                '_eq': user.id
            }
        }
    }
));

Cómo funciona la aplicación

Cuando el usuario marca el número de Vonage asignado a nuestra aplicación, los servidores de Vonage obtienen la URL de respuesta para nuestra aplicación y realizan una llamada a nuestro servidor. Para nuestra aplicación, este es el archivo src/routes/CallStart.js archivo. La URL de respuesta devuelve un NCCO o un objeto de control de llamadas. Un NCCO es simplemente un blob JSON con instrucciones para que tomen los servidores de Vonage. Al iniciar una llamada, devolvemos un NCCO que tiene una Talk acción y una Input acción. Le preguntamos al usuario qué quiere hacer y esperamos a que nos lo diga.

También consultamos la API de Directus para ver si ya tienen una entrada. Si es así, les recordamos la próxima película.

// src/routes/CallStart.js

router.post('/', async (req, res) => {
    const builder = new NCCOBuilder();
    const directus = getDirectusClient();

    const user = await getUserFromPhone(req.body.from);
    const tickets = await directus.request(readItems(
        'tickets',
        {
            'fields': [
                'showtime_id.*.*'
            ],
            'sort': [
                'showtime_id.start_time'
            ],
            'filter': {
                'user': {
                    '_eq': user.id
                }
            }
        }
    ));

    let openingMessage = "Welcome! What can we help you with?";

    if (tickets.length > 0) {
        const startTime = new Date(tickets[0].showtime_id.start_time);
        const timeString = `${startTime.getFullYear()} ${startTime.getMonth() + 1} ${startTime.getDay()} at ${startTime.getHours()} ${startTime.getMinutes()}`;
        openingMessage = `Welcome back! We look forward to seeing you on ${timeString} for ${tickets[0].showtime_id.movie_id.title}. What can we help you with?`;
    }

    const domain = getDomain(req);
    return res.send(
        builder
            .addAction(new Talk(openingMessage))
            .addAction(new Input(
                null,
                {
                    'context': ['cancel ticket']
                },
                `${domain}/determine_action`))
            .build()
    );
});

Además de llamar a la API de Directus, usamos la herramienta NCCOBuilder que viene con el SDK de Vonage Node para ayudarnos a construir el JSON que necesitamos para la NCCO. Añadimos una acción Talk para dar el mensaje de bienvenida, y luego una acción Input que escucha la voz del usuario y la convierte en una cadena. También podríamos hacer DTMF o entrada a través del teclado de marcación, pero por ahora, vamos a utilizar voz a texto.

Cuando el usuario nos dice lo que quiere hacer (y como se trata de una demostración, solo admitimos la cancelación de un ticket), los servidores de Vonage toman esa entrada de voz y la convierten en una solicitud JSON que la envía a la URL determinada por nuestra Input acción, que es <ngrok URL>/determine_action. La solicitud entrante tendrá este aspecto:

{
    "speech": {
        "timeout_reason": "end_on_silence_timeout",
        "results": [
            {
                "confidence": "0.7276162",
                "text": "cancel ticket"
            }
        ]
    },
    "dtmf": {
        "digits": null,
        "timed_out": false
    },
    "from": "15556661234",
    "to": "18005556666",
    "uuid": "ee9d7f5daa81673b5461c6f5XXXXXXXX",
    "conversation_uuid": "CON-baa6dcee-6750-4cde-82d2-XXXXXXXXXXXX",
    "timestamp": "2023-12-22T03:47:55.378Z"
}

Este blob JSON se envía a nuestro servidor y es gestionado por src/routes/DetermineAction.js.

// src/routes/DetermineAction.js
router.post('/determine_action', async (req, res) => {
    const builder = new NCCOBuilder();

    if (req.body.speech?.results[0].text === 'cancel ticket') {
        const directus = getDirectusClient();
        const user = await getUserFromPhone(req.body.from);
        const tickets = await directus.request(readItems(
            'tickets',
            {
                'fields': [
                    'id',
                    'showtime_id.*.*'
                ],
                'sort': [
                    'showtime_id.start_time'
                ],
                'filter': {
                    'user': {
                        '_eq': user.id
                    }
                }
            }
        ));

        if (tickets.length === 0) {
            builder
                .addAction(new Talk('We do not see any tickets for you at the moment. Would you like to hear a list of available movies to purchase a ticket?'))
                .addAction(new Input(
                    null,
                    {
                        'context': ['yes', 'no']
                    },
                    `${getDomain(req)}/list_movies`)
                )
        } else if (tickets.length === 1) {
            builder
                .addAction(new Talk(`Would you like to cancel your ticket for ${tickets[0].showtime_id.movie_id.title}?`))
                .addAction(new Input(
                    null,
                    {
                        'context': ['yes', 'no']
                    },
                    `${getDomain(req)}/confirm_cancel?id=${tickets[0].id}`)
                )
        } else {
            // Ask which upcoming tickets to cancel
        }
    } else if (req.body.speech.results[0].text === 'buy ticket') {
        builder
            .addAction(new Talk('Would you like to hear a list of available movies to purchase a ticket?'))
            .addAction(new Input(
                null,
                {
                    'context': ['yes', 'no']
                },
                `${getDomain(req)}/list_movies`)
            )
    } else {
        builder.addAction(new Talk('Sorry, I do not understand what you would like to do.'));
        builder.addAction(new Notify(
            {
                action: 'restart',
                from: req.body.from,
            },
            `${getDomain(req)}/notify`
        ));
    }

    return res.send(builder.build());
});

Esta ruta es un poco más complicada, ya que intentamos averiguar qué ha hecho el usuario. El código de demostración admite la cancelación de un billete, así como la compra de uno nuevo.

Cuando se cancela una entrada, se muestran las entradas que el usuario ha comprado. Si no ha comprado ninguna, le preguntamos si desea comprar una. En caso afirmativo, le empujamos al flujo de trabajo de compra.

Si sólo tienen un ticket, les preguntamos si desean cancelarlo. Este es el flujo de trabajo que detallamos en este post, por lo que devolvemos un NCCO con otro Talk y Input confirmando la cancelación, y si el usuario confirma la cancelación, volvemos a llamar a la API de Directus para eliminar el ticket.

También pasamos el ID de la película en la URL como parámetro de consulta. HTTP en sí mismo no tiene estado, y normalmente, utilizaríamos una sesión para realizar un seguimiento de los flujos. Dado que la Voice API no funciona con sesiones, mantenemos el estado a la antigua usanza pasando parámetros de consulta. Pasamos el ID del ticket a la ruta de confirmación mediante un parámetro de consulta.

Si el usuario tuviera más de un billete, le pediríamos que seleccionara qué billete desea cancelar, pero por ahora dejaremos esa lógica fuera, ya que complica la demostración.

// src/routes/ConfirmCancel.js

router.post('/confirm_cancel', async (req, res) => {
    const builder = new NCCOBuilder();

    if (req.body.speech.results[0].text === 'yes') {
        const directus = getDirectusClient();
        directus.request(deleteItem('tickets', req.query.id));

        builder
            .addAction(new Talk('We have removed your ticket'))
            .build();
    }

    builder.addAction(new Notify(
        {
            action: 'restart',
            from: req.body.from,
        },
        `${getDomain(req)}/notify`
    ));
    return res.send(builder.build());

Cuando llegamos a la ruta de cancelación, tomamos el ID del ticket de los parámetros de consulta y enviamos una deleteItem() a Directus. Esto elimina el billete de la base de datos de Directus, e informamos al cliente de que hemos cancelado su billete.

Podríamos terminar la llamada ahí mismo, pero para ser útiles, transferimos al usuario de vuelta al principio del flujo de trabajo. Para ello, enviamos una Notify como una NCCO, lo que nos permite inyectar una NCCO en el flujo.

// src/routes/Notify
router.all('/notify', async (req, res) => {
    const builder = new NCCOBuilder();

    const domain = getDomain(req);
    return res.send(
        builder
            .addAction(new Talk('What else can we help you with?'))
            .addAction(new Input(
                null,
                {
                    'context': ['cancel ticket', 'buy ticket']
                },
                `${domain}/determine_action`))
            .build()
    );
});

Esta NCCO de Notificación se limita a preguntar al usuario si hay algo más en lo que podamos ayudarle y reenvía esa respuesta a la ruta para determinar la acción que desea realizar, igual que si el usuario acabara de llamar. El usuario puede colgar en cualquier momento para finalizar la llamada.

Conclusión

Cuando crees una aplicación, aprovecha tus puntos fuertes. Preocúpate de construir la lógica de tu aplicación y deja que otros servicios se ocupen de las partes de tu aplicación que no son tu fuerte. Directus es una forma estupenda de gestionar los datos, y las Voice API de Vonage eliminan las complicaciones de la telefonía. Únete a la conversación en nuestra Slack de la comunidad de desarrolladores de Vonage o envíanos un mensaje en X, antes conocido como Twitter.

Compartir:

https://a.storyblok.com/f/270183/384x384/3bc39cbd62/christankersley.png
Chris TankersleyGestor de herramientas de relaciones con los desarrolladores

Chris es el director de herramientas de relaciones con los desarrolladores y dirige el equipo que crea sus herramientas favoritas. Lleva más de 15 años programando en varios lenguajes y tipos de proyectos, desde trabajos para clientes hasta sistemas de big data a gran escala. Vive en Ohio, donde pasa el tiempo con su familia y jugando a Video y TTRPG.