Rust API - Banco de dados e Migrations - Part II


Hoje vamos dar continuidade ao que fizemos com base no nosso healtchecker, vamos adicionar a nossa rota para criar uma nova task, antes de criarmos de fato a rota vamos adicionar algumas env’s a nossa aplicação, pra isso é só rodar o comando dentro da raiz do nosso repositório.

touch .env

E vamos adicionar algumas variáveis aqui:

POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=6500
POSTGRES_USER=admin
POSTGRES_PASSWORD=password123
POSTGRES_DB=rust_admin

DATABASE_URL=postgresql://admin:password123@localhost:6500/rust_admin?schema=public

PGADMIN_DEFAULT_EMAIL=admin@admin.com
PGADMIN_DEFAULT_PASSWORD=password123

Essas variáveis serão as mesmas que iremos usar na construção da nossa aplicação, principalmente a criação e acesso ao banco de dados.

No próximo momento, nós iremos criar um docker-compose.yml para criar o nosso banco postgres:

touch docker-compose.yml

Dentro do arquivo:

version: '3'
services:
  postgres:
    image: postgres:latest
    container_name: postgres
    ports:
      - '6500:5432'
    volumes:
      - progresDB:/data/postgres
    env_file:
      - ./.env
  pgAdmin:
    image: dpage/pgadmin4
    container_name: pgAdmin
    env_file:
      - ./.env
    ports:
      - "5050:80"
volumes:
  progresDB:

Nesse momento você já consegue rodar o comando de docker-compose up -d e ter o seu banco rodando:

imagem-do-banco-rodando

Antes de começarmos, vou somente adicionar na nossa main o que iremos criar logo em seguida, o import do nosso schema e model:

src/main.rs

mod services;
mod model;
mod schema;

Após isso iremos nós criar um novo arquivo chamado schema.rs onde teremos a base do nosso body para criar uma nova request

touch src/schema.rs
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct CreateTaskSchema {
    pub title: String,
    pub content: String,
}

Repare que nele eu estou fazendo o import do Deserialize, Serialize que ambos serão usados para definir que nosso struct terá um novo schema onde poderemos fazer a leitura dos dados de um body json.

Logo em seguida nós iremos definir a nossa model:

touch src/model.rs
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use uuid::Uuid;

#[derive(Debug, FromRow, Deserialize, Serialize)]
pub struct TaskModel {
    pub id: Uuid,
    pub title: String,
    pub content: String,
    pub created_at: Option<chrono::DateTime<chrono::Utc>>,
}

Aqui vamos ter a definição de como será a nossa estrutura junto ao nosso banco de dados, você pode ver que temos para os nossos ids um uuid, title como string, content como string e usamos o NaiveDateTime importado do nosso chrono.

Bem, antes de continuarmos precisaremos instalar o sqlx-cli para os próximos passos:

cargo install sqlx-cli

Nesse momento, nós precisamos criar as nossas tables no nosso banco de dados, e a quanto a isso iremos nos utilizar do sqlx:

sqlx migrate add -r init

Esse comando irá nos gerar dois arquivos em uma nova pasta chamada migrate, nela adicionaremos duas queries em sql, uma para up:

-- Add up migration script here
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE
    IF NOT EXISTS tasks (
        id UUID PRIMARY KEY NOT NULL DEFAULT (uuid_generate_v4()),
        title VARCHAR(255) NOT NULL,
        content TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('brt'::text, now())
      );

E a de down para caso eventualmente queira dropar essa mesma tabela. (Por favor, cuidado em realizar o mesmo em produção).

DROP TABLE tasks;

Após isso é rodar o comando abaixo para criar as tabelas no nosso banco de dados:

sqlx migrate run

E você consegue ver que foi criada a nossa tabela: table-task

E se necessário for, rodar o outro comando abaixo para reverter as tabelas:

sqlx migrate revert

Creio que por hora é só, espero que tenha gostado e um grande abraço =]